Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,203 +1,59 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
import numpy as np
|
| 4 |
-
|
| 5 |
-
|
|
|
|
| 6 |
from ydata_profiling import ProfileReport
|
| 7 |
from streamlit_pandas_profiling import st_profile_report
|
| 8 |
-
import
|
| 9 |
-
import requests
|
| 10 |
-
import json
|
| 11 |
-
from datetime import datetime
|
| 12 |
-
import re
|
| 13 |
-
import tempfile
|
| 14 |
-
from scipy import stats
|
| 15 |
-
from sklearn.impute import SimpleImputer
|
| 16 |
-
from sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder
|
| 17 |
from sklearn.decomposition import PCA
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
# Load environment variables
|
| 27 |
-
load_dotenv()
|
| 28 |
-
|
| 29 |
-
# Initialize Flask app
|
| 30 |
-
flask_app = Flask(__name__)
|
| 31 |
-
FLASK_PORT = 5000 # Internal port for Flask, not exposed externally
|
| 32 |
-
|
| 33 |
-
# Initialize OpenAI client
|
| 34 |
-
api_key = os.getenv("OPENAI_API_KEY")
|
| 35 |
-
if not api_key:
|
| 36 |
-
st.error("OPENAI_API_KEY not set. Please configure it in the Hugging Face Space secrets.")
|
| 37 |
-
st.stop()
|
| 38 |
-
client = OpenAI(api_key=api_key)
|
| 39 |
-
|
| 40 |
-
# Flask RAG Endpoint
|
| 41 |
-
@flask_app.route('/rag_chat', methods=['POST'])
|
| 42 |
-
def rag_chat():
|
| 43 |
-
data = request.get_json()
|
| 44 |
-
user_input = data.get('user_input', '')
|
| 45 |
-
app_mode = data.get('app_mode', 'Data Upload')
|
| 46 |
-
dataset_text = data.get('dataset_text', '')
|
| 47 |
-
|
| 48 |
-
# RAG Logic: Use dataset_text as retrieval context
|
| 49 |
-
system_prompt = (
|
| 50 |
-
"You are an AI assistant in Data-Vision Pro, a data analysis app with RAG capabilities. "
|
| 51 |
-
"The app has three pages:\n"
|
| 52 |
-
"- **Data Upload**: Upload CSV/XLSX files, view stats, or generate reports.\n"
|
| 53 |
-
"- **Data Cleaning**: Clean data (e.g., handle missing values, encode variables).\n"
|
| 54 |
-
"- **EDA**: Visualize data (e.g., scatter plots, histograms).\n"
|
| 55 |
-
f"The user is on the '{app_mode}' page.\n"
|
| 56 |
-
)
|
| 57 |
-
|
| 58 |
-
if dataset_text:
|
| 59 |
-
system_prompt += (
|
| 60 |
-
"Using the following dataset context, augment your response:\n"
|
| 61 |
-
f"{dataset_text}\n"
|
| 62 |
-
"Answer based on this data where relevant, otherwise provide general assistance."
|
| 63 |
-
)
|
| 64 |
-
else:
|
| 65 |
-
system_prompt += "No dataset is loaded. Assist based on app functionality."
|
| 66 |
-
|
| 67 |
-
try:
|
| 68 |
-
response = client.chat.completions.create(
|
| 69 |
-
model="gpt-3.5-turbo",
|
| 70 |
-
messages=[
|
| 71 |
-
{"role": "system", "content": system_prompt},
|
| 72 |
-
{"role": "user", "content": user_input}
|
| 73 |
-
],
|
| 74 |
-
max_tokens=100, # Increased for RAG context
|
| 75 |
-
temperature=0.7
|
| 76 |
-
)
|
| 77 |
-
return jsonify({"response": response.choices[0].message.content})
|
| 78 |
-
except Exception as e:
|
| 79 |
-
return jsonify({"error": str(e)}), 500
|
| 80 |
-
|
| 81 |
-
# Run Flask in a background thread
|
| 82 |
-
def run_flask():
|
| 83 |
-
flask_app.run(host='0.0.0.0', port=FLASK_PORT, debug=False, use_reloader=False)
|
| 84 |
-
|
| 85 |
-
# Start Flask thread
|
| 86 |
-
flask_thread = threading.Thread(target=run_flask, daemon=True)
|
| 87 |
-
flask_thread.start()
|
| 88 |
-
|
| 89 |
-
# Helper Functions
|
| 90 |
-
def enhance_section_title(title):
|
| 91 |
-
st.markdown(f"<h2 style='border-bottom: 2px solid #ccc; padding-bottom: 5px;'>{title}</h2>", unsafe_allow_html=True)
|
| 92 |
|
| 93 |
-
def update_cleaned_data(df):
|
| 94 |
-
st.session_state.cleaned_data = df
|
| 95 |
-
if 'data_versions' not in st.session_state:
|
| 96 |
-
st.session_state.data_versions = [st.session_state.raw_data.copy()]
|
| 97 |
-
st.session_state.data_versions.append(df.copy())
|
| 98 |
-
st.success("✅ Action completed successfully!")
|
| 99 |
-
st.rerun()
|
| 100 |
-
|
| 101 |
-
def convert_csv_to_json_and_text(df):
|
| 102 |
-
"""Convert DataFrame to JSON and then to plain text."""
|
| 103 |
-
json_data = df.to_json(orient="records")
|
| 104 |
-
data_dict = json.loads(json_data)
|
| 105 |
-
text_summary = f"Dataset Summary: {df.shape[0]} rows, {df.shape[1]} columns\n"
|
| 106 |
-
text_summary += f"Missing Values: {df.isna().sum().sum()}\n"
|
| 107 |
-
text_summary += "Columns:\n"
|
| 108 |
-
for col in df.columns:
|
| 109 |
-
text_summary += f"- {col} ({df[col].dtype}): "
|
| 110 |
-
if pd.api.types.is_numeric_dtype(df[col]):
|
| 111 |
-
text_summary += f"Mean={df[col].mean():.2f}, Min={df[col].min()}, Max={df[col].max()}"
|
| 112 |
-
else:
|
| 113 |
-
text_summary += f"Unique={df[col].nunique()}, Top={df[col].mode()[0] if not df[col].mode().empty else 'N/A'}"
|
| 114 |
-
text_summary += f", Missing={df[col].isna().sum()}\n"
|
| 115 |
-
return text_summary
|
| 116 |
-
|
| 117 |
-
def get_chatbot_response(user_input, app_mode, dataset_text=""):
|
| 118 |
-
"""Send request to internal Flask RAG endpoint."""
|
| 119 |
-
payload = {
|
| 120 |
-
"user_input": user_input,
|
| 121 |
-
"app_mode": app_mode,
|
| 122 |
-
"dataset_text": dataset_text
|
| 123 |
-
}
|
| 124 |
-
try:
|
| 125 |
-
response = requests.post(f"http://localhost:{FLASK_PORT}/rag_chat", json=payload, timeout=5)
|
| 126 |
-
response.raise_for_status()
|
| 127 |
-
return response.json().get("response", "Error: No response from server")
|
| 128 |
-
except requests.exceptions.RequestException as e:
|
| 129 |
-
return f"Error: Could not connect to RAG server. {str(e)}"
|
| 130 |
-
|
| 131 |
-
# Streamlit App
|
| 132 |
# Sidebar Navigation
|
| 133 |
with st.sidebar:
|
| 134 |
-
st.title("🔮
|
| 135 |
-
st.markdown("Your AI-powered
|
| 136 |
st.markdown("---")
|
| 137 |
-
app_mode = st.selectbox(
|
| 138 |
-
|
| 139 |
-
["Data Upload", "Data Cleaning", "EDA"],
|
| 140 |
-
format_func=lambda x: f"📌 {x}"
|
| 141 |
-
)
|
| 142 |
-
if app_mode == "Data Upload":
|
| 143 |
-
st.info("⬆️ Upload your CSV or XLSX dataset to begin.")
|
| 144 |
-
elif app_mode == "Data Cleaning":
|
| 145 |
-
st.info("🧹 Clean and preprocess your data using various tools.")
|
| 146 |
-
elif app_mode == "EDA":
|
| 147 |
-
st.info("🔍 Explore your data visually and statistically.")
|
| 148 |
-
|
| 149 |
st.markdown("---")
|
| 150 |
-
st.markdown("**
|
| 151 |
-
|
| 152 |
-
csv = st.session_state.cleaned_data.to_csv(index=False)
|
| 153 |
-
st.download_button(
|
| 154 |
-
label="Download Cleaned Data as CSV",
|
| 155 |
-
data=csv,
|
| 156 |
-
file_name='cleaned_data.csv',
|
| 157 |
-
mime='text/csv',
|
| 158 |
-
)
|
| 159 |
-
st.markdown("Created by Calvin Allen-Crawford")
|
| 160 |
-
st.markdown("v1.0 | © 2025")
|
| 161 |
-
|
| 162 |
-
# Main App Pages
|
| 163 |
-
if app_mode == "Data Upload":
|
| 164 |
-
st.title("📤 Data Upload & Profiling")
|
| 165 |
-
st.header("Upload Your Dataset")
|
| 166 |
-
st.write("Supported formats: CSV, XLSX")
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
if uploaded_file:
|
| 173 |
-
|
| 174 |
-
st.session_state
|
| 175 |
-
st.session_state
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
st.session_state.dataset_text = convert_csv_to_json_and_text(df)
|
| 186 |
-
if 'data_versions' not in st.session_state:
|
| 187 |
-
st.session_state.data_versions = [df.copy()]
|
| 188 |
-
col1, col2, col3 = st.columns(3)
|
| 189 |
-
with col1: st.metric("Rows", df.shape[0])
|
| 190 |
-
with col2: st.metric("Columns", df.shape[1])
|
| 191 |
-
with col3: st.metric("Missing Values", df.isna().sum().sum())
|
| 192 |
-
if st.checkbox("Show Data Preview"):
|
| 193 |
-
st.dataframe(df.head(10), use_container_width=True)
|
| 194 |
-
if st.button("Generate Full Profile Report"):
|
| 195 |
-
with st.spinner("Generating report..."):
|
| 196 |
-
pr = ProfileReport(df, explorative=True)
|
| 197 |
-
st_profile_report(pr)
|
| 198 |
-
st.success("✅ Data loaded successfully!")
|
| 199 |
-
except Exception as e:
|
| 200 |
-
st.error(f"An error occurred: {str(e)}")
|
| 201 |
|
| 202 |
elif app_mode == "Data Cleaning":
|
| 203 |
st.title("🧹 Smart Data Cleaning")
|
|
@@ -223,9 +79,165 @@ elif app_mode == "Data Cleaning":
|
|
| 223 |
if st.button("Undo Last Action"):
|
| 224 |
st.session_state.data_versions.pop()
|
| 225 |
st.session_state.cleaned_data = st.session_state.data_versions[-1].copy()
|
| 226 |
-
st.session_state.dataset_text = convert_csv_to_json_and_text(st.session_state.cleaned_data)
|
| 227 |
st.rerun()
|
| 228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
elif app_mode == "EDA":
|
| 230 |
st.title("🔍 Interactive Data Explorer")
|
| 231 |
if 'cleaned_data' not in st.session_state:
|
|
@@ -233,7 +245,10 @@ elif app_mode == "EDA":
|
|
| 233 |
st.stop()
|
| 234 |
df = st.session_state.cleaned_data.copy()
|
| 235 |
|
|
|
|
| 236 |
enhance_section_title("Dataset Overview")
|
|
|
|
|
|
|
| 237 |
with st.container():
|
| 238 |
col1, col2, col3, col4 = st.columns(4)
|
| 239 |
col1.metric("Total Rows", df.shape[0])
|
|
@@ -242,26 +257,197 @@ elif app_mode == "EDA":
|
|
| 242 |
col3.metric("Missing Values", f"{df.isna().sum().sum()} ({missing_percentage:.1f}%)")
|
| 243 |
col4.metric("Duplicates", df.duplicated().sum())
|
| 244 |
|
| 245 |
-
#
|
| 246 |
-
st.
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
with st.
|
| 267 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
import numpy as np
|
| 4 |
+
from pycaret.classification import setup as classification_setup, compare_models as compare_classification_models, evaluate_model as evaluate_classification_model, save_model as save_classification_model, plot_model as plot_classification_model
|
| 5 |
+
from pycaret.regression import setup as regression_setup, compare_models as compare_regression_models, evaluate_model as evaluate_regression_model, save_model as save_regression_model, plot_model as plot_regression_model
|
| 6 |
+
from pycaret.clustering import setup as clustering_setup, evaluate_model as evaluate_clustering_model, save_model as save_clustering_model, plot_model as plot_clustering_model
|
| 7 |
from ydata_profiling import ProfileReport
|
| 8 |
from streamlit_pandas_profiling import st_profile_report
|
| 9 |
+
from sklearn.preprocessing import LabelEncoder, StandardScaler, MinMaxScaler
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from sklearn.decomposition import PCA
|
| 11 |
+
from scipy import stats
|
| 12 |
+
import plotly.express as px
|
| 13 |
+
import plotly.graph_objects as go
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
# Set page config
|
| 17 |
+
st.set_page_config(page_title="Neural-Vision Enhanced", layout="wide")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
# Sidebar Navigation
|
| 20 |
with st.sidebar:
|
| 21 |
+
st.title("🔮 Neural-Vision Enhanced")
|
| 22 |
+
st.markdown("Your AI-powered model toolbox.")
|
| 23 |
st.markdown("---")
|
| 24 |
+
app_mode = st.selectbox("Navigation", ["Data Upload", "Data Cleaning", "EDA", "Model Training", "Validation & Exploration"])
|
| 25 |
+
data_type = st.selectbox("Data Type", ["Tabular"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
st.markdown("---")
|
| 27 |
+
st.markdown("**Dependencies**: `pycaret`, `pandas`, `streamlit`, `ydata-profiling`, `sklearn`, `plotly`")
|
| 28 |
+
st.markdown("Created by Calvin Allen-Crawford | v2.0 | © 2025")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
# Helper functions
|
| 31 |
+
def update_cleaned_data(new_df):
|
| 32 |
+
if 'data_versions' not in st.session_state:
|
| 33 |
+
st.session_state.data_versions = []
|
| 34 |
+
st.session_state.data_versions.append(new_df)
|
| 35 |
+
st.session_state['cleaned_data'] = new_df
|
| 36 |
+
|
| 37 |
+
def enhance_section_title(title):
|
| 38 |
+
st.markdown(f"<h2 style='text-align: center; color: #1e3a8a;'>{title}</h2>", unsafe_allow_html=True)
|
| 39 |
+
|
| 40 |
+
# Main App Sections
|
| 41 |
+
if app_mode == "Data Upload":
|
| 42 |
+
st.title("📤 Data Upload")
|
| 43 |
+
uploaded_file = st.file_uploader("Upload CSV Dataset", type=["csv"])
|
| 44 |
if uploaded_file:
|
| 45 |
+
df = pd.read_csv(uploaded_file)
|
| 46 |
+
st.session_state['raw_data'] = df
|
| 47 |
+
st.session_state['cleaned_data'] = df.copy()
|
| 48 |
+
st.write("---")
|
| 49 |
+
st.subheader("Dataset Preview")
|
| 50 |
+
st.dataframe(df.head(10))
|
| 51 |
+
st.write("---")
|
| 52 |
+
st.subheader("Statistics")
|
| 53 |
+
col1, col2, col3 = st.columns(3)
|
| 54 |
+
with col1: st.metric("Rows", df.shape[0])
|
| 55 |
+
with col2: st.metric("Columns", df.shape[1])
|
| 56 |
+
with col3: st.metric("Missing Values", df.isna().sum().sum())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
elif app_mode == "Data Cleaning":
|
| 59 |
st.title("🧹 Smart Data Cleaning")
|
|
|
|
| 79 |
if st.button("Undo Last Action"):
|
| 80 |
st.session_state.data_versions.pop()
|
| 81 |
st.session_state.cleaned_data = st.session_state.data_versions[-1].copy()
|
|
|
|
| 82 |
st.rerun()
|
| 83 |
|
| 84 |
+
with st.expander("🛠️ Data Cleaning Operations", expanded=True):
|
| 85 |
+
enhance_section_title("🔍 Missing Values Treatment")
|
| 86 |
+
missing_cols = df.columns[df.isna().any()].tolist()
|
| 87 |
+
if missing_cols:
|
| 88 |
+
cols = st.multiselect("Select columns with missing values", missing_cols)
|
| 89 |
+
method = st.selectbox("Choose imputation method", [
|
| 90 |
+
"Drop Missing Values", "Fill with Mean/Median", "Fill with Custom Value", "Forward Fill", "Backward Fill"
|
| 91 |
+
])
|
| 92 |
+
if method == "Fill with Custom Value":
|
| 93 |
+
custom_val = st.text_input("Enter custom value:")
|
| 94 |
+
if st.button("Apply Missing Value Treatment"):
|
| 95 |
+
new_df = df.copy()
|
| 96 |
+
if method == "Drop Missing Values":
|
| 97 |
+
new_df = new_df.dropna(subset=cols)
|
| 98 |
+
elif method == "Fill with Mean/Median":
|
| 99 |
+
for col in cols:
|
| 100 |
+
if pd.api.types.is_numeric_dtype(new_df[col]):
|
| 101 |
+
new_df[col] = new_df[col].fillna(new_df[col].median())
|
| 102 |
+
else:
|
| 103 |
+
new_df[col] = new_df[col].fillna(new_df[col].mode()[0])
|
| 104 |
+
elif method == "Fill with Custom Value" and custom_val:
|
| 105 |
+
new_df[cols] = new_df[cols].fillna(custom_val)
|
| 106 |
+
elif method == "Forward Fill":
|
| 107 |
+
new_df[cols] = new_df[cols].ffill()
|
| 108 |
+
elif method == "Backward Fill":
|
| 109 |
+
new_df[cols] = new_df[cols].bfill()
|
| 110 |
+
update_cleaned_data(new_df)
|
| 111 |
+
else:
|
| 112 |
+
st.success("✨ No missing values detected!")
|
| 113 |
+
|
| 114 |
+
enhance_section_title("🔄 Data Type Conversion")
|
| 115 |
+
col_to_convert = st.selectbox("Select column to convert", df.columns)
|
| 116 |
+
new_type = st.selectbox("Select new data type", ["String", "Integer", "Float", "Boolean", "Datetime"])
|
| 117 |
+
if new_type == "Datetime":
|
| 118 |
+
date_format = st.text_input("Enter date format (e.g., %Y-%m-%d):", "%Y-%m-%d")
|
| 119 |
+
if st.button("Convert Data Type"):
|
| 120 |
+
new_df = df.copy()
|
| 121 |
+
if new_type == "String":
|
| 122 |
+
new_df[col_to_convert] = new_df[col_to_convert].astype(str)
|
| 123 |
+
elif new_type == "Integer":
|
| 124 |
+
new_df[col_to_convert] = pd.to_numeric(new_df[col_to_convert], errors='coerce').astype('Int64')
|
| 125 |
+
elif new_type == "Float":
|
| 126 |
+
new_df[col_to_convert] = pd.to_numeric(new_df[col_to_convert], errors='coerce')
|
| 127 |
+
elif new_type == "Boolean":
|
| 128 |
+
new_df[col_to_convert] = new_df[col_to_convert].astype(bool)
|
| 129 |
+
elif new_type == "Datetime":
|
| 130 |
+
new_df[col_to_convert] = pd.to_datetime(new_df[col_to_convert], format=date_format, errors='coerce')
|
| 131 |
+
update_cleaned_data(new_df)
|
| 132 |
+
|
| 133 |
+
enhance_section_title("🗑️ Drop Columns")
|
| 134 |
+
columns_to_drop = st.multiselect("Select columns to remove", df.columns)
|
| 135 |
+
if columns_to_drop and st.button("Confirm Column Removal"):
|
| 136 |
+
new_df = df.copy()
|
| 137 |
+
new_df = new_df.drop(columns=columns_to_drop)
|
| 138 |
+
update_cleaned_data(new_df)
|
| 139 |
+
|
| 140 |
+
enhance_section_title("🔢 Encoding Options")
|
| 141 |
+
encoding_method = st.radio("Choose encoding method", ("Label Encoding", "One-Hot Encoding"))
|
| 142 |
+
data_to_encode = st.multiselect("Select columns to encode", df.select_dtypes(include='object').columns)
|
| 143 |
+
if data_to_encode and st.button("Apply Encoding"):
|
| 144 |
+
new_df = df.copy()
|
| 145 |
+
if encoding_method == "Label Encoding":
|
| 146 |
+
for col in data_to_encode:
|
| 147 |
+
le = LabelEncoder()
|
| 148 |
+
new_df[col] = le.fit_transform(new_df[col].astype(str))
|
| 149 |
+
elif encoding_method == "One-Hot Encoding":
|
| 150 |
+
new_df = pd.get_dummies(new_df, columns=data_to_encode, drop_first=True, dtype=int)
|
| 151 |
+
update_cleaned_data(new_df)
|
| 152 |
+
|
| 153 |
+
enhance_section_title("📏 StandardScaler")
|
| 154 |
+
scale_cols = st.multiselect("Select numerical columns to scale", df.select_dtypes(include=np.number).columns)
|
| 155 |
+
if scale_cols and st.button("Apply StandardScaler"):
|
| 156 |
+
new_df = df.copy()
|
| 157 |
+
scaler = StandardScaler()
|
| 158 |
+
new_df[scale_cols] = scaler.fit_transform(new_df[scale_cols])
|
| 159 |
+
update_cleaned_data(new_df)
|
| 160 |
+
|
| 161 |
+
enhance_section_title("🕵️ Pattern-Based Cleaning")
|
| 162 |
+
selected_col = st.selectbox("Select text column for pattern cleaning", df.select_dtypes(include='object').columns)
|
| 163 |
+
pattern = st.text_input("Enter regex pattern:")
|
| 164 |
+
replacement = st.text_input("Enter replacement value:")
|
| 165 |
+
if st.button("Apply Pattern Replacement"):
|
| 166 |
+
new_df = df.copy()
|
| 167 |
+
new_df[selected_col] = new_df[selected_col].str.replace(pattern, replacement, regex=True)
|
| 168 |
+
update_cleaned_data(new_df)
|
| 169 |
+
|
| 170 |
+
enhance_section_title("🚀 Bulk Actions")
|
| 171 |
+
bulk_action = st.selectbox("Choose bulk action", [
|
| 172 |
+
"Auto-Clean Common Issues", "Drop All Missing Values", "Fill All Missing Values",
|
| 173 |
+
"One-Hot Encode All Categorical Columns", "Apply Min-Max Scaling to All Numeric Columns",
|
| 174 |
+
"Remove Outliers from All Numeric Columns", "Principal Component Analysis (PCA)"
|
| 175 |
+
])
|
| 176 |
+
if bulk_action == "Auto-Clean Common Issues" and st.button("Run Auto-Clean"):
|
| 177 |
+
new_df = df.copy()
|
| 178 |
+
new_df = new_df.dropna(axis=1, how='all')
|
| 179 |
+
new_df = new_df.convert_dtypes()
|
| 180 |
+
text_cols = new_df.select_dtypes(include='object').columns
|
| 181 |
+
new_df[text_cols] = new_df[text_cols].apply(lambda x: x.str.strip())
|
| 182 |
+
update_cleaned_data(new_df)
|
| 183 |
+
elif bulk_action == "Drop All Missing Values" and st.button("Drop All Missing"):
|
| 184 |
+
new_df = df.copy()
|
| 185 |
+
new_df = new_df.dropna()
|
| 186 |
+
update_cleaned_data(new_df)
|
| 187 |
+
elif bulk_action == "Fill All Missing Values":
|
| 188 |
+
fill_value = st.text_input("Enter fill value:", "0")
|
| 189 |
+
if st.button("Fill Missing Values"):
|
| 190 |
+
new_df = df.copy()
|
| 191 |
+
new_df = new_df.fillna(fill_value)
|
| 192 |
+
update_cleaned_data(new_df)
|
| 193 |
+
elif bulk_action == "One-Hot Encode All Categorical Columns" and st.button("One-Hot Encode All"):
|
| 194 |
+
new_df = df.copy()
|
| 195 |
+
categorical_cols = new_df.select_dtypes(include='object').columns
|
| 196 |
+
new_df = pd.get_dummies(new_df, columns=categorical_cols, drop_first=True, dtype=int)
|
| 197 |
+
update_cleaned_data(new_df)
|
| 198 |
+
elif bulk_action == "Apply Min-Max Scaling to All Numeric Columns" and st.button("Apply Min-Max Scaling"):
|
| 199 |
+
new_df = df.copy()
|
| 200 |
+
scaler = MinMaxScaler()
|
| 201 |
+
numerical_cols = new_df.select_dtypes(include=np.number).columns
|
| 202 |
+
new_df[numerical_cols] = scaler.fit_transform(new_df[numerical_cols])
|
| 203 |
+
update_cleaned_data(new_df)
|
| 204 |
+
elif bulk_action == "Remove Outliers from All Numeric Columns" and st.button("Remove All Outliers"):
|
| 205 |
+
new_df = df.copy()
|
| 206 |
+
z_scores = np.abs(stats.zscore(new_df.select_dtypes(include=np.number)))
|
| 207 |
+
new_df = new_df[(z_scores < 3).all(axis=1)]
|
| 208 |
+
update_cleaned_data(new_df)
|
| 209 |
+
elif bulk_action == "Principal Component Analysis (PCA)":
|
| 210 |
+
n_components_bulk = st.slider("Number of components", 1, min(df.shape[1], 10), 2)
|
| 211 |
+
if st.button("Apply PCA (Bulk)"):
|
| 212 |
+
new_df = df.copy()
|
| 213 |
+
pca = PCA(n_components=n_components_bulk)
|
| 214 |
+
numerical_cols = new_df.select_dtypes(include=np.number).columns
|
| 215 |
+
pca_result = pca.fit_transform(new_df[numerical_cols])
|
| 216 |
+
new_df = pd.DataFrame(pca_result, columns=[f'PC{i+1}' for i in range(pca_result.shape[1])])
|
| 217 |
+
update_cleaned_data(new_df.reset_index(drop=True))
|
| 218 |
+
|
| 219 |
+
enhance_section_title("📊 Principal Component Analysis (PCA)")
|
| 220 |
+
numerical_cols = df.select_dtypes(include=np.number).columns.tolist()
|
| 221 |
+
if numerical_cols:
|
| 222 |
+
pca_cols = st.multiselect("Select columns for PCA", numerical_cols, default=numerical_cols)
|
| 223 |
+
if pca_cols:
|
| 224 |
+
st.subheader("Covariance Matrix Heatmap")
|
| 225 |
+
cov_matrix = df[pca_cols].cov()
|
| 226 |
+
fig_cov = px.imshow(cov_matrix, labels=dict(x="Features", y="Features", color="Covariance"), color_continuous_scale='RdBu_r')
|
| 227 |
+
st.plotly_chart(fig_cov)
|
| 228 |
+
n_components = st.slider("Number of components", 1, min(len(pca_cols), 10), 2)
|
| 229 |
+
if st.button("Apply PCA"):
|
| 230 |
+
new_df = df.copy()
|
| 231 |
+
scaler = StandardScaler()
|
| 232 |
+
scaled_data = scaler.fit_transform(new_df[pca_cols])
|
| 233 |
+
pca = PCA(n_components=n_components)
|
| 234 |
+
pca_result = pca.fit_transform(scaled_data)
|
| 235 |
+
pca_df = pd.DataFrame(pca_result, columns=[f'PC{i+1}' for i in range(n_components)])
|
| 236 |
+
update_cleaned_data(pca_df.reset_index(drop=True))
|
| 237 |
+
st.write("Explained Variance Ratio:", pca.explained_variance_ratio_)
|
| 238 |
+
else:
|
| 239 |
+
st.warning("No numerical columns available for PCA.")
|
| 240 |
+
|
| 241 |
elif app_mode == "EDA":
|
| 242 |
st.title("🔍 Interactive Data Explorer")
|
| 243 |
if 'cleaned_data' not in st.session_state:
|
|
|
|
| 245 |
st.stop()
|
| 246 |
df = st.session_state.cleaned_data.copy()
|
| 247 |
|
| 248 |
+
# Enhanced Section Title
|
| 249 |
enhance_section_title("Dataset Overview")
|
| 250 |
+
|
| 251 |
+
# Dataset Overview with More Visual Appeal
|
| 252 |
with st.container():
|
| 253 |
col1, col2, col3, col4 = st.columns(4)
|
| 254 |
col1.metric("Total Rows", df.shape[0])
|
|
|
|
| 257 |
col3.metric("Missing Values", f"{df.isna().sum().sum()} ({missing_percentage:.1f}%)")
|
| 258 |
col4.metric("Duplicates", df.duplicated().sum())
|
| 259 |
|
| 260 |
+
# Tabs for Quick Preview, Column Types, and Missing Matrix
|
| 261 |
+
tab1, tab2, tab3 = st.tabs(["Quick Preview", "Column Types", "Missing Matrix"])
|
| 262 |
+
|
| 263 |
+
with tab1:
|
| 264 |
+
st.write("First few rows of the dataset:")
|
| 265 |
+
st.dataframe(df.head(), use_container_width=True)
|
| 266 |
+
|
| 267 |
+
with tab2:
|
| 268 |
+
st.write("Column Data Types:")
|
| 269 |
+
type_counts = df.dtypes.value_counts().reset_index()
|
| 270 |
+
type_counts.columns = ['Type', 'Count']
|
| 271 |
+
st.dataframe(type_counts, use_container_width=True)
|
| 272 |
+
|
| 273 |
+
with tab3:
|
| 274 |
+
st.write("Missing Values Matrix:")
|
| 275 |
+
fig_missing = px.imshow(df.isna(), color_continuous_scale=['#e0e0e0', '#FF4B4B'])
|
| 276 |
+
st.plotly_chart(fig_missing, use_container_width=True)
|
| 277 |
+
|
| 278 |
+
# Enhanced Visualization Builder Section
|
| 279 |
+
enhance_section_title("Interactive Visualization Builder")
|
| 280 |
+
|
| 281 |
+
with st.container():
|
| 282 |
+
col1, col2 = st.columns([1, 3])
|
| 283 |
+
with col1:
|
| 284 |
+
plot_type = st.selectbox("Choose visualization type", [
|
| 285 |
+
"Scatter Plot", "Histogram", "Box Plot", "Violin Plot", "Line Chart", "Bar Chart",
|
| 286 |
+
"Correlation Matrix", "Pair Plot", "Heatmap", "3D Scatter", "Parallel Categories",
|
| 287 |
+
"Segmented Bar Chart", "Swarm Plot", "Ridge Plot", "Bubble Plot", "Density Plot",
|
| 288 |
+
"Count Plot", "Lollipop Chart"
|
| 289 |
+
])
|
| 290 |
+
x_axis = st.selectbox("X-axis", df.columns) if plot_type not in ["Correlation Matrix", "Pair Plot"] else None
|
| 291 |
+
y_axis = st.selectbox("Y-axis", df.columns) if plot_type in ["Scatter Plot", "Box Plot", "Violin Plot", "Line Chart", "Heatmap", "Swarm Plot", "Ridge Plot", "Bubble Plot", "Density Plot", "Lollipop Chart"] else None
|
| 292 |
+
z_axis = st.selectbox("Z-axis", df.columns) if plot_type == "3D Scatter" else None
|
| 293 |
+
color_by = st.selectbox("Color encoding", ["None"] + df.columns.tolist(), format_func=lambda x: "No color" if x == "None" else x) if plot_type not in ["Correlation Matrix", "Pair Plot"] else None
|
| 294 |
+
|
| 295 |
+
if plot_type == "Parallel Categories":
|
| 296 |
+
dimensions = st.multiselect("Dimensions", df.columns.tolist(), default=df.columns[:3].tolist())
|
| 297 |
+
elif plot_type == "Segmented Bar Chart":
|
| 298 |
+
segment_col = st.selectbox("Segment Column (Categorical)", df.select_dtypes(exclude=np.number).columns)
|
| 299 |
+
elif plot_type == "Bubble Plot":
|
| 300 |
+
size_col = st.selectbox("Size Column", df.columns)
|
| 301 |
+
elif plot_type == "Pair Plot":
|
| 302 |
+
pair_cols = st.multiselect("Select columns for Pair Plot", df.columns, default=df.columns[:5].tolist())
|
| 303 |
+
|
| 304 |
+
with col2:
|
| 305 |
+
try:
|
| 306 |
+
fig = None
|
| 307 |
+
if plot_type == "Scatter Plot" and x_axis and y_axis:
|
| 308 |
+
fig = px.scatter(df, x=x_axis, y=y_axis, color=color_by if color_by != "None" else None, trendline="lowess", title=f'Scatter Plot of {x_axis} vs {y_axis}')
|
| 309 |
+
elif plot_type == "Histogram" and x_axis:
|
| 310 |
+
fig = px.histogram(df, x=x_axis, color=color_by if color_by != "None" else None, nbins=30, marginal="box", title=f'Histogram of {x_axis}')
|
| 311 |
+
elif plot_type == "Box Plot" and x_axis and y_axis:
|
| 312 |
+
fig = px.box(df, x=x_axis, y=y_axis, color=color_by if color_by != "None" else None, title=f'Box Plot of {x_axis} vs {y_axis}')
|
| 313 |
+
elif plot_type == "Violin Plot" and x_axis and y_axis:
|
| 314 |
+
fig = px.violin(df, x=x_axis, y=y_axis, color=color_by if color_by != "None" else None, box=True, title=f'Violin Plot of {x_axis} vs {y_axis}')
|
| 315 |
+
elif plot_type == "Line Chart" and x_axis and y_axis:
|
| 316 |
+
fig = px.line(df, x=x_axis, y=y_axis, color=color_by if color_by != "None" else None, title=f'Line Chart of {x_axis} vs {y_axis}')
|
| 317 |
+
elif plot_type == "Bar Chart" and x_axis:
|
| 318 |
+
fig = px.bar(df, x=x_axis, color=color_by if color_by != "None" else None, title=f'Bar Chart of {x_axis}')
|
| 319 |
+
elif plot_type == "Correlation Matrix":
|
| 320 |
+
numeric_df = df.select_dtypes(include=np.number)
|
| 321 |
+
if len(numeric_df.columns) > 1:
|
| 322 |
+
corr = numeric_df.corr()
|
| 323 |
+
fig = px.imshow(corr, text_auto=True, color_continuous_scale='RdBu_r', zmin=-1, zmax=1, title='Correlation Matrix')
|
| 324 |
+
elif plot_type == "Pair Plot":
|
| 325 |
+
if pair_cols:
|
| 326 |
+
fig = px.scatter_matrix(df[pair_cols], color=color_by if color_by != "None" else None, title='Pair Plot')
|
| 327 |
+
elif plot_type == "Heatmap" and x_axis and y_axis:
|
| 328 |
+
fig = px.density_heatmap(df, x=x_axis, y=y_axis, facet_col=color_by if color_by != "None" else None, title=f'Heatmap of {x_axis} vs {y_axis}')
|
| 329 |
+
elif plot_type == "3D Scatter" and x_axis and y_axis and z_axis:
|
| 330 |
+
fig = px.scatter_3d(df, x=x_axis, y=y_axis, z=z_axis, color=color_by if color_by != "None" else None, title=f'3D Scatter Plot of {x_axis} vs {y_axis} vs {z_axis}')
|
| 331 |
+
elif plot_type == "Parallel Categories" and dimensions:
|
| 332 |
+
fig = px.parallel_categories(df, dimensions=dimensions, color=color_by if color_by != "None" else None, title='Parallel Categories Plot')
|
| 333 |
+
elif plot_type == "Segmented Bar Chart" and x_axis and segment_col:
|
| 334 |
+
segment_counts = df.groupby([x_axis, segment_col]).size().reset_index(name='counts')
|
| 335 |
+
fig = px.bar(segment_counts, x=x_axis, y='counts', color=segment_col, title=f'Segmented Bar Chart of {x_axis} by {segment_col}')
|
| 336 |
+
fig.update_layout(yaxis_title="Count")
|
| 337 |
+
elif plot_type == "Swarm Plot" and x_axis and y_axis:
|
| 338 |
+
fig = px.strip(df, x=x_axis, y=y_axis, color=color_by if color_by != "None" else None, title=f'Swarm Plot of {x_axis} vs {y_axis}')
|
| 339 |
+
elif plot_type == "Ridge Plot" and x_axis and y_axis:
|
| 340 |
+
fig = px.histogram(df, x=x_axis, color=y_axis, marginal="rug", title=f'Ridge Plot of {x_axis} by {y_axis}')
|
| 341 |
+
elif plot_type == "Bubble Plot" and x_axis and y_axis and size_col:
|
| 342 |
+
fig = px.scatter(df, x=x_axis, y=y_axis, size=size_col, color=color_by if color_by != "None" else None, title=f'Bubble Plot of {x_axis} vs {y_axis}')
|
| 343 |
+
elif plot_type == "Density Plot" and x_axis and y_axis:
|
| 344 |
+
fig = px.density_heatmap(df, x=x_axis, y=y_axis, color_continuous_scale="Viridis", title=f'Density Plot of {x_axis} vs {y_axis}')
|
| 345 |
+
elif plot_type == "Count Plot" and x_axis:
|
| 346 |
+
fig = px.bar(df, x=x_axis, color=color_by if color_by != "None" else None, title=f'Count Plot of {x_axis}')
|
| 347 |
+
fig.update_layout(yaxis_title="Count")
|
| 348 |
+
elif plot_type == "Lollipop Chart" and x_axis and y_axis:
|
| 349 |
+
fig = go.Figure()
|
| 350 |
+
fig.add_trace(go.Scatter(x=df[x_axis], y=df[y_axis], mode='markers', marker=dict(size=10)))
|
| 351 |
+
for i in range(len(df)):
|
| 352 |
+
fig.add_trace(go.Scatter(x=[df[x_axis].iloc[i], df[x_axis].iloc[i]], y=[0, df[y_axis].iloc[i]], mode='lines', line=dict(color='gray')))
|
| 353 |
+
fig.update_layout(showlegend=False, title=f'Lollipop Chart of {x_axis} vs {y_axis}')
|
| 354 |
+
|
| 355 |
+
if fig:
|
| 356 |
+
fig.update_layout(template="plotly_white")
|
| 357 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 358 |
+
else:
|
| 359 |
+
st.error("Please provide required inputs for the selected plot type.")
|
| 360 |
+
except Exception as e:
|
| 361 |
+
st.error(f"Couldn't create visualization: {str(e)}")
|
| 362 |
+
|
| 363 |
+
elif app_mode == "Model Training":
|
| 364 |
+
st.title("🧠 Model Training")
|
| 365 |
+
if 'cleaned_data' not in st.session_state:
|
| 366 |
+
st.warning("Please upload and clean data first.")
|
| 367 |
+
st.stop()
|
| 368 |
+
df = st.session_state.cleaned_data.copy()
|
| 369 |
+
problem_type = st.selectbox("Problem Type", ["Classification", "Regression", "Clustering"])
|
| 370 |
+
target = st.selectbox("Select Target Column", df.columns) if problem_type != "Clustering" else None
|
| 371 |
+
|
| 372 |
+
if st.button("Setup PyCaret"):
|
| 373 |
+
with st.spinner("Setting up PyCaret..."):
|
| 374 |
+
if problem_type == "Classification":
|
| 375 |
+
classification_setup(data=df, target=target, session_id=123, verbose=False)
|
| 376 |
+
st.session_state['problem_type'] = "Classification"
|
| 377 |
+
st.session_state['setup_complete'] = True
|
| 378 |
+
elif problem_type == "Regression":
|
| 379 |
+
regression_setup(data=df, target=target, session_id=123, verbose=False)
|
| 380 |
+
st.session_state['problem_type'] = "Regression"
|
| 381 |
+
st.session_state['setup_complete'] = True
|
| 382 |
+
elif problem_type == "Clustering":
|
| 383 |
+
clustering_setup(data=df, session_id=123, verbose=False)
|
| 384 |
+
st.session_state['problem_type'] = "Clustering"
|
| 385 |
+
st.session_state['setup_complete'] = True
|
| 386 |
+
st.success("PyCaret setup complete! You can now train models.")
|
| 387 |
+
|
| 388 |
+
if st.session_state.get('setup_complete', False):
|
| 389 |
+
st.subheader("Train Models")
|
| 390 |
+
if st.button("Compare Models"):
|
| 391 |
+
with st.spinner("Comparing models..."):
|
| 392 |
+
if st.session_state['problem_type'] == "Classification":
|
| 393 |
+
best_model = compare_classification_models()
|
| 394 |
+
elif st.session_state['problem_type'] == "Regression":
|
| 395 |
+
best_model = compare_regression_models()
|
| 396 |
+
elif st.session_state['problem_type'] == "Clustering":
|
| 397 |
+
st.info("Model comparison is not available for clustering. Please proceed with evaluation or create a model manually.")
|
| 398 |
+
best_model = None
|
| 399 |
+
else:
|
| 400 |
+
best_model = None
|
| 401 |
+
if best_model is not None:
|
| 402 |
+
st.session_state['best_model'] = best_model
|
| 403 |
+
st.success(f"Best Model: {best_model}")
|
| 404 |
+
|
| 405 |
+
if 'best_model' in st.session_state and st.session_state['best_model'] is not None:
|
| 406 |
+
st.subheader("Model Evaluation")
|
| 407 |
+
if st.button("Evaluate Model"):
|
| 408 |
+
with st.spinner("Evaluating model..."):
|
| 409 |
+
if st.session_state['problem_type'] == "Classification":
|
| 410 |
+
evaluate_classification_model(st.session_state['best_model'])
|
| 411 |
+
elif st.session_state['problem_type'] == "Regression":
|
| 412 |
+
evaluate_regression_model(st.session_state['best_model'])
|
| 413 |
+
elif st.session_state['problem_type'] == "Clustering":
|
| 414 |
+
evaluate_clustering_model(st.session_state['best_model'])
|
| 415 |
+
st.success("Model evaluation complete!")
|
| 416 |
+
|
| 417 |
+
if st.button("Save Model"):
|
| 418 |
+
if st.session_state['problem_type'] == "Classification":
|
| 419 |
+
save_classification_model(st.session_state['best_model'], "best_model")
|
| 420 |
+
elif st.session_state['problem_type'] == "Regression":
|
| 421 |
+
save_regression_model(st.session_state['best_model'], "best_model")
|
| 422 |
+
elif st.session_state['problem_type'] == "Clustering":
|
| 423 |
+
save_clustering_model(st.session_state['best_model'], "best_model")
|
| 424 |
+
st.success("Model saved as `best_model.pkl`!")
|
| 425 |
+
with open("best_model.pkl", "rb") as f:
|
| 426 |
+
st.download_button("Download Model", f, file_name="best_model.pkl")
|
| 427 |
+
|
| 428 |
+
elif app_mode == "Validation & Exploration":
|
| 429 |
+
st.title("🔍 Validation & Exploration")
|
| 430 |
+
if 'best_model' not in st.session_state or st.session_state['best_model'] is None:
|
| 431 |
+
st.warning("Please train a model first. Note: Clustering does not support automatic model comparison.")
|
| 432 |
+
st.stop()
|
| 433 |
+
|
| 434 |
+
st.subheader("Model Performance")
|
| 435 |
+
if st.session_state['problem_type'] == "Classification":
|
| 436 |
+
st.write("Classification Report:")
|
| 437 |
+
plot_classification_model(st.session_state['best_model'], plot="confusion_matrix", display_format="streamlit")
|
| 438 |
+
plot_classification_model(st.session_state['best_model'], plot="auc", display_format="streamlit")
|
| 439 |
+
elif st.session_state['problem_type'] == "Regression":
|
| 440 |
+
st.write("Regression Metrics:")
|
| 441 |
+
plot_regression_model(st.session_state['best_model'], plot="residuals", display_format="streamlit")
|
| 442 |
+
plot_regression_model(st.session_state['best_model'], plot="error", display_format="streamlit")
|
| 443 |
+
elif st.session_state['problem_type'] == "Clustering":
|
| 444 |
+
st.write("Clustering Results:")
|
| 445 |
+
plot_clustering_model(st.session_state['best_model'], plot="cluster", display_format="streamlit")
|
| 446 |
+
|
| 447 |
+
# Custom CSS
|
| 448 |
+
st.markdown("""
|
| 449 |
+
<style>
|
| 450 |
+
.stButton>button {background-color: #4CAF50; color: white;}
|
| 451 |
+
h1, h2 {color: #1e3a8a;}
|
| 452 |
+
</style>
|
| 453 |
+
""", unsafe_allow_html=True)
|