CosmickVisions commited on
Commit
369feea
·
verified ·
1 Parent(s): 9e431a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +455 -425
app.py CHANGED
@@ -6,113 +6,187 @@ import plotly.graph_objects as go
6
  from ydata_profiling import ProfileReport
7
  from streamlit_pandas_profiling import st_profile_report
8
  import os
9
- import requests
10
- import json
 
 
 
 
11
  import re
12
  from scipy import stats
13
  from sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder
14
- from sklearn.decomposition import PCA
15
- from dotenv import load_dotenv
16
- from flask import Flask, request, jsonify
17
- from openai import OpenAI
18
- import threading
19
 
20
  # Load environment variables
21
  load_dotenv()
22
 
23
- # Initialize Flask app
24
- flask_app = Flask(__name__)
25
- FLASK_PORT = 5000 # Internal port for Flask, not exposed externally
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- # Flask RAG Endpoint
28
- @flask_app.route('/rag_chat', methods=['POST'])
29
- def rag_chat():
30
- data = request.get_json()
31
- user_input = data.get('user_input', '')
32
- app_mode = data.get('app_mode', 'Data Upload')
33
- dataset_text = data.get('dataset_text', '')
 
 
 
 
 
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  system_prompt = (
36
  "You are an AI assistant in Data-Vision Pro, a data analysis app with RAG capabilities. "
37
- "The app has three pages:\n"
38
  "- **Data Upload**: Upload CSV/XLSX files, view stats, or generate reports.\n"
39
  "- **Data Cleaning**: Clean data (e.g., handle missing values, encode variables).\n"
40
  "- **EDA**: Visualize data (e.g., scatter plots, histograms).\n"
41
- f"The user is on the '{app_mode}' page.\n"
42
  )
43
-
44
- if dataset_text:
45
- system_prompt += (
46
- "Using the following dataset context, augment your response:\n"
47
- f"{dataset_text}\n"
48
- "Answer based on this data where relevant, otherwise provide general assistance."
49
- )
50
  else:
51
  system_prompt += "No dataset is loaded. Assist based on app functionality."
52
-
53
  try:
54
  response = client.chat.completions.create(
55
- model="gpt-3.5-turbo",
56
  messages=[
57
  {"role": "system", "content": system_prompt},
58
  {"role": "user", "content": user_input}
59
  ],
60
- max_tokens=100,
61
- temperature=0.7
62
  )
63
- return jsonify({"response": response.choices[0].message.content})
64
  except Exception as e:
65
- return jsonify({"error": str(e)}), 500
66
-
67
- # Run Flask in a background thread
68
- def run_flask():
69
- flask_app.run(host='0.0.0.0', port=FLASK_PORT, debug=False, use_reloader=False)
70
-
71
- flask_thread = threading.Thread(target=run_flask, daemon=True)
72
- flask_thread.start()
73
-
74
- # Helper Functions
75
- def enhance_section_title(title):
76
- st.markdown(f"<h2 style='border-bottom: 2px solid #ccc; padding-bottom: 5px;'>{title}</h2>", unsafe_allow_html=True)
77
 
78
- def update_cleaned_data(df):
79
- st.session_state.cleaned_data = df
80
- if 'data_versions' not in st.session_state:
81
- st.session_state.data_versions = [st.session_state.raw_data.copy()]
82
- st.session_state.data_versions.append(df.copy())
83
- st.session_state.dataset_text = convert_csv_to_json_and_text(df)
84
- st.success("✅ Action completed successfully!")
85
- st.rerun()
86
-
87
- def convert_csv_to_json_and_text(df):
88
- json_data = df.to_json(orient="records")
89
- data_dict = json.loads(json_data)
90
- text_summary = f"Dataset Summary: {df.shape[0]} rows, {df.shape[1]} columns\n"
91
- text_summary += f"Missing Values: {df.isna().sum().sum()}\n"
92
- text_summary += "Columns:\n"
93
- for col in df.columns:
94
- text_summary += f"- {col} ({df[col].dtype}): "
95
- if pd.api.types.is_numeric_dtype(df[col]):
96
- text_summary += f"Mean={df[col].mean():.2f}, Min={df[col].min()}, Max={df[col].max()}"
97
- else:
98
- text_summary += f"Unique={df[col].nunique()}, Top={df[col].mode()[0] if not df[col].mode().empty else 'N/A'}"
99
- text_summary += f", Missing={df[col].isna().sum()}\n"
100
- return text_summary
101
-
102
- def get_chatbot_response(user_input, app_mode, dataset_text=""):
103
- payload = {
104
- "user_input": user_input,
105
- "app_mode": app_mode,
106
- "dataset_text": dataset_text
107
- }
108
- try:
109
- response = requests.post(f"http://localhost:{FLASK_PORT}/rag_chat", json=payload, timeout=5)
110
- response.raise_for_status()
111
- return response.json().get("response", "Error: No response from server")
112
- except requests.exceptions.RequestException as e:
113
- return f"Error: Could not connect to RAG server. {str(e)}"
114
-
115
- # Command Functions for LLM
116
  def drop_columns(columns):
117
  if 'cleaned_data' in st.session_state:
118
  df = st.session_state.cleaned_data.copy()
@@ -126,7 +200,6 @@ def drop_columns(columns):
126
  return "No valid columns found to drop."
127
  return "No dataset loaded."
128
 
129
- # LLM-Driven EDA Commands
130
  def generate_scatter_plot(params):
131
  df = st.session_state.cleaned_data
132
  match = re.search(r"([\w\s]+)\s+vs\s+([\w\s]+)", params)
@@ -149,7 +222,6 @@ def generate_histogram(params):
149
  return f"Generated histogram of {x_axis}"
150
  return "Invalid column for histogram."
151
 
152
- # Inference from Plotted Data
153
  def analyze_plot():
154
  if "last_plot" not in st.session_state:
155
  return "No plot available to analyze."
@@ -170,7 +242,6 @@ def analyze_plot():
170
  return f"The histogram of {x_col} is {skew_desc} (skewness = {skewness:.2f})."
171
  return "Inference not available for this plot type."
172
 
173
- # Parse Chatbot Commands
174
  def parse_command(command):
175
  command = command.lower().strip()
176
  if "drop columns" in command or "drop column" in command:
@@ -184,358 +255,317 @@ def parse_command(command):
184
  return generate_histogram, params
185
  elif "analyze plot" in command:
186
  return lambda x: analyze_plot(), None
187
- return None, "Command not recognized. Try 'drop columns X, Y', 'scatter plot of X vs Y', or 'analyze plot'."
188
 
189
  # Dataset Preview Function
190
  def display_dataset_preview():
191
  if 'cleaned_data' in st.session_state:
192
  st.subheader("Current Dataset Preview")
193
  st.dataframe(st.session_state.cleaned_data.head(10), use_container_width=True)
194
- st.write("---")
195
-
196
- # Sidebar Navigation with API Key Input
197
- with st.sidebar:
198
- st.title("🔮 Data-Vision Pro")
199
- st.markdown("Your AI-powered data analysis suite with RAG.")
200
- st.markdown("---")
201
- app_mode = st.selectbox(
202
- "Navigation",
203
- ["Data Upload", "Data Cleaning", "EDA"],
204
- format_func=lambda x: f"📌 {x}"
205
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  if app_mode == "Data Upload":
207
- st.info("⬆️ Upload your CSV or XLSX dataset to begin.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  elif app_mode == "Data Cleaning":
209
- st.info("🧹 Clean and preprocess your data using various tools.")
210
- elif app_mode == "EDA":
211
- st.info("🔍 Explore your data visually and statistically.")
212
-
213
- # API Key Input Field
214
- api_key_input = st.text_input(
215
- "Enter your API key (optional)",
216
- type="password",
217
- help="Enter your API key to override the default. Leave blank to use the app's default key."
218
- )
219
 
220
- st.markdown("---")
221
- st.markdown("**Note**: Requires dependencies in `requirements.txt`.")
222
- if 'cleaned_data' in st.session_state:
223
- csv = st.session_state.cleaned_data.to_csv(index=False)
224
- st.download_button(
225
- label="Download Cleaned Data as CSV",
226
- data=csv,
227
- file_name='cleaned_data.csv',
228
- mime='text/csv',
229
- )
230
- st.markdown("Created by Calvin Allen-Crawford")
231
- st.markdown("v1.0 | © 2025")
232
-
233
- # Determine which API key to use
234
- if api_key_input:
235
- api_key = api_key_input # Use the user-provided API key from the sidebar
236
- else:
237
- api_key = st.secrets.get("OPENAI_API_KEY", os.getenv("OPENAI_API_KEY")) # Fall back to secret or environment variable
238
-
239
- if not api_key:
240
- st.error("API key is required. Please provide it in the sidebar or ensure it’s set in the app’s secrets.")
241
- st.stop()
242
-
243
- # Initialize OpenAI client with the selected API key
244
- client = OpenAI(api_key=api_key)
245
-
246
- # Display dataset preview at the top of each page
247
- display_dataset_preview()
248
-
249
- # Main App Pages
250
- if app_mode == "Data Upload":
251
- st.title("📤 Data Upload & Profiling")
252
- st.header("Upload Your Dataset")
253
- st.write("Supported formats: CSV, XLSX")
254
- if 'raw_data' not in st.session_state:
255
- st.info("It looks like no dataset has been uploaded yet. Would you like to upload a CSV or XLSX file?")
256
- uploaded_file = st.file_uploader("Choose a file", type=["csv", "xlsx"], key="file_uploader")
257
- if uploaded_file:
258
- st.session_state.pop('raw_data', None)
259
- st.session_state.pop('cleaned_data', None)
260
- st.session_state.pop('data_versions', None)
261
- try:
262
- if uploaded_file.name.endswith('.csv'):
263
- df = pd.read_csv(uploaded_file)
264
- else:
265
- df = pd.read_excel(uploaded_file)
266
- if df.empty:
267
- st.error("Uploaded file is empty.")
268
- st.stop()
269
- st.session_state.raw_data = df
270
- st.session_state.cleaned_data = df.copy()
271
- st.session_state.dataset_text = convert_csv_to_json_and_text(df)
272
- if 'data_versions' not in st.session_state:
273
- st.session_state.data_versions = [df.copy()]
274
  col1, col2, col3 = st.columns(3)
275
- with col1: st.metric("Rows", df.shape[0])
276
- with col2: st.metric("Columns", df.shape[1])
277
  with col3: st.metric("Missing Values", df.isna().sum().sum())
278
- if st.checkbox("Show Data Preview"):
279
- st.dataframe(df.head(10), use_container_width=True)
280
- if st.button("Generate Full Profile Report"):
281
  with st.spinner("Generating report..."):
282
- pr = ProfileReport(df, explorative=True)
283
- st_profile_report(pr)
284
- st.success("✅ Data loaded successfully!")
285
- except Exception as e:
286
- st.error(f"An error occurred: {str(e)}")
287
-
288
- elif app_mode == "Data Cleaning":
289
- st.title("🧹 Smart Data Cleaning")
290
- st.header("Preprocess and Transform Your Data")
291
- if 'raw_data' not in st.session_state:
292
- st.warning("Please upload data first in the Data Upload section.")
293
- st.stop()
294
- if 'cleaned_data' not in st.session_state:
295
- st.session_state.cleaned_data = st.session_state.raw_data.copy()
296
- df = st.session_state.cleaned_data.copy()
297
-
298
- enhance_section_title("📊 Data Health Dashboard")
299
- with st.expander("Explore Data Health Metrics", expanded=True):
300
- col1, col2, col3 = st.columns(3)
301
- with col1: st.metric("Columns", len(df.columns))
302
- with col2: st.metric("Rows", len(df))
303
- with col3: st.metric("Missing Values", df.isna().sum().sum())
304
- if st.button("Generate Detailed Health Report"):
305
- with st.spinner("Generating report..."):
306
- profile = ProfileReport(df, minimal=True)
307
- st_profile_report(profile)
308
- if 'data_versions' in st.session_state and len(st.session_state.data_versions) > 1:
309
- if st.button("Undo Last Action"):
310
- st.session_state.data_versions.pop()
311
- st.session_state.cleaned_data = st.session_state.data_versions[-1].copy()
312
- st.session_state.dataset_text = convert_csv_to_json_and_text(st.session_state.cleaned_data)
313
- st.rerun()
314
-
315
- with st.expander("🛠️ Data Cleaning Operations", expanded=True):
316
- enhance_section_title("🔍 Missing Values Treatment")
317
- missing_cols = df.columns[df.isna().any()].tolist()
318
- if missing_cols:
319
- cols = st.multiselect("Select columns with missing values", missing_cols)
320
- method = st.selectbox("Choose imputation method", [
321
- "Drop Missing Values", "Fill with Mean/Median", "Fill with Custom Value", "Forward Fill", "Backward Fill"
322
- ])
323
- if method == "Fill with Custom Value":
324
- custom_val = st.text_input("Enter custom value:")
325
- if st.button("Apply Missing Value Treatment"):
 
 
326
  new_df = df.copy()
327
- if method == "Drop Missing Values":
328
- new_df = new_df.dropna(subset=cols)
329
- elif method == "Fill with Mean/Median":
330
- for col in cols:
331
- if pd.api.types.is_numeric_dtype(new_df[col]):
332
- new_df[col] = new_df[col].fillna(new_df[col].median())
333
- else:
334
- new_df[col] = new_df[col].fillna(new_df[col].mode()[0])
335
- elif method == "Fill with Custom Value" and custom_val:
336
- new_df[cols] = new_df[cols].fillna(custom_val)
337
- elif method == "Forward Fill":
338
- new_df[cols] = new_df[cols].ffill()
339
- elif method == "Backward Fill":
340
- new_df[cols] = new_df[cols].bfill()
341
  update_cleaned_data(new_df)
342
- else:
343
- st.success(" No missing values detected!")
344
-
345
- enhance_section_title("🔄 Data Type Conversion")
346
- col_to_convert = st.selectbox("Select column to convert", df.columns)
347
- new_type = st.selectbox("Select new data type", ["String", "Integer", "Float", "Boolean", "Datetime"])
348
- if new_type == "Datetime":
349
- date_format = st.text_input("Enter date format (e.g., %Y-%m-%d):", "%Y-%m-%d")
350
- if st.button("Convert Data Type"):
351
- new_df = df.copy()
352
- if new_type == "String":
353
- new_df[col_to_convert] = new_df[col_to_convert].astype(str)
354
- elif new_type == "Integer":
355
- new_df[col_to_convert] = pd.to_numeric(new_df[col_to_convert], errors='coerce').astype('Int64')
356
- elif new_type == "Float":
357
- new_df[col_to_convert] = pd.to_numeric(new_df[col_to_convert], errors='coerce')
358
- elif new_type == "Boolean":
359
- new_df[col_to_convert] = new_df[col_to_convert].astype(bool)
360
- elif new_type == "Datetime":
361
- new_df[col_to_convert] = pd.to_datetime(new_df[col_to_convert], format=date_format, errors='coerce')
362
- update_cleaned_data(new_df)
363
-
364
- enhance_section_title("🗑️ Drop Columns")
365
- columns_to_drop = st.multiselect("Select columns to remove", df.columns)
366
- if columns_to_drop and st.button("Confirm Column Removal"):
367
- new_df = df.copy()
368
- new_df = new_df.drop(columns=columns_to_drop)
369
- update_cleaned_data(new_df)
370
-
371
- enhance_section_title("🔢 Encoding Options")
372
- encoding_method = st.radio("Choose encoding method", ("Label Encoding", "One-Hot Encoding"))
373
- data_to_encode = st.multiselect("Select columns to encode", df.select_dtypes(include='object').columns)
374
- if data_to_encode and st.button("Apply Encoding"):
375
- new_df = df.copy()
376
- if encoding_method == "Label Encoding":
377
- for col in data_to_encode:
378
- le = LabelEncoder()
379
- new_df[col] = le.fit_transform(new_df[col].astype(str))
380
- elif encoding_method == "One-Hot Encoding":
381
- new_df = pd.get_dummies(new_df, columns=data_to_encode, drop_first=True, dtype=int)
382
- update_cleaned_data(new_df)
383
-
384
- enhance_section_title("📏 StandardScaler")
385
- scale_cols = st.multiselect("Select numerical columns to scale", df.select_dtypes(include=np.number).columns)
386
- if scale_cols and st.button("Apply StandardScaler"):
387
- new_df = df.copy()
388
- scaler = StandardScaler()
389
- new_df[scale_cols] = scaler.fit_transform(new_df[scale_cols])
390
- update_cleaned_data(new_df)
391
-
392
- enhance_section_title("🕵️ Pattern-Based Cleaning")
393
- selected_col = st.selectbox("Select text column for pattern cleaning", df.select_dtypes(include='object').columns)
394
- pattern = st.text_input("Enter regex pattern:")
395
- replacement = st.text_input("Enter replacement value:")
396
- if st.button("Apply Pattern Replacement"):
397
- new_df = df.copy()
398
- new_df[selected_col] = new_df[selected_col].str.replace(pattern, replacement, regex=True)
399
- update_cleaned_data(new_df)
400
-
401
- elif app_mode == "EDA":
402
- st.title("🔍 Interactive Data Explorer")
403
- if 'cleaned_data' not in st.session_state:
404
- st.warning("Please upload and clean data first.")
405
- st.stop()
406
- df = st.session_state.cleaned_data.copy()
407
-
408
- enhance_section_title("Dataset Overview")
409
- with st.container():
410
- col1, col2, col3, col4 = st.columns(4)
411
- col1.metric("Total Rows", df.shape[0])
412
- col2.metric("Total Columns", df.shape[1])
413
- missing_percentage = df.isna().sum().sum() / df.size * 100
414
- col3.metric("Missing Values", f"{df.isna().sum().sum()} ({missing_percentage:.1f}%)")
415
- col4.metric("Duplicates", df.duplicated().sum())
416
-
417
- tab1, tab2, tab3 = st.tabs(["Quick Preview", "Column Types", "Missing Matrix"])
418
- with tab1:
419
- st.write("First few rows of the dataset:")
420
- st.dataframe(df.head(), use_container_width=True)
421
- with tab2:
422
- st.write("Column Data Types:")
423
- type_counts = df.dtypes.value_counts().reset_index()
424
- type_counts.columns = ['Type', 'Count']
425
- st.dataframe(type_counts, use_container_width=True)
426
- with tab3:
427
- st.write("Missing Values Matrix:")
428
- fig_missing = px.imshow(df.isna(), color_continuous_scale=['#e0e0e0', '#66c2a5'])
429
- fig_missing.update_layout(coloraxis_colorscale=[[0, 'lightgrey'], [1, '#FF4B4B']])
430
- st.plotly_chart(fig_missing, use_container_width=True)
431
-
432
- enhance_section_title("Interactive Visualization Builder")
433
- with st.container():
434
- col1, col2 = st.columns([1, 3])
435
- with col1:
436
- plot_type = st.selectbox("Choose visualization type", [
437
- "Scatter Plot", "Histogram", "Box Plot", "Violin Plot", "Line Chart", "Bar Chart",
438
- "Correlation Matrix", "Heatmap", "3D Scatter", "Parallel Categories", "Segmented Bar Chart",
439
- "Swarm Plot", "Ridge Plot", "Bubble Plot", "Density Plot", "Count Plot", "Lollipop Chart"
440
- ])
441
- x_axis = st.selectbox("X-axis", df.columns) if plot_type != "Correlation Matrix" else None
442
- 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
443
- z_axis = st.selectbox("Z-axis", df.columns) if plot_type == "3D Scatter" else None
444
- color_by = st.selectbox("Color encoding", ["None"] + df.columns.tolist(), format_func=lambda x: "No color" if x == "None" else x) if plot_type != "Correlation Matrix" else None
445
- if plot_type == "Parallel Categories":
446
- dimensions = st.multiselect("Dimensions", df.columns.tolist(), default=df.columns[:3].tolist())
447
- elif plot_type == "Segmented Bar Chart":
448
- segment_col = st.selectbox("Segment Column (Categorical)", df.select_dtypes(exclude=np.number).columns)
449
- elif plot_type == "Bubble Plot":
450
- size_col = st.selectbox("Size Column", df.columns)
451
-
452
- with col2:
453
- try:
454
- fig = None
455
- if plot_type == "Scatter Plot" and x_axis and y_axis:
456
- 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}')
457
- elif plot_type == "Histogram" and x_axis:
458
- 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}')
459
- elif plot_type == "Box Plot" and x_axis and y_axis:
460
- 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}')
461
- elif plot_type == "Violin Plot" and x_axis and y_axis:
462
- 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}')
463
- elif plot_type == "Line Chart" and x_axis and y_axis:
464
- 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}')
465
- elif plot_type == "Bar Chart" and x_axis:
466
- fig = px.bar(df, x=x_axis, color=color_by if color_by != "None" else None, title=f'Bar Chart of {x_axis}')
467
- elif plot_type == "Correlation Matrix":
468
- numeric_df = df.select_dtypes(include=np.number)
469
- if len(numeric_df.columns) > 1:
470
- corr = numeric_df.corr()
471
- fig = px.imshow(corr, text_auto=True, color_continuous_scale='RdBu_r', zmin=-1, zmax=1, title='Correlation Matrix')
472
- elif plot_type == "Heatmap" and x_axis and y_axis:
473
- 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}')
474
- elif plot_type == "3D Scatter" and x_axis and y_axis and z_axis:
475
- 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}')
476
- elif plot_type == "Parallel Categories" and dimensions:
477
- fig = px.parallel_categories(df, dimensions=dimensions, color=color_by if color_by != "None" else None, title='Parallel Categories Plot')
478
- elif plot_type == "Segmented Bar Chart" and x_axis and segment_col:
479
- segment_counts = df.groupby([x_axis, segment_col]).size().reset_index(name='counts')
480
- fig = px.bar(segment_counts, x=x_axis, y='counts', color=segment_col, title=f'Segmented Bar Chart of {x_axis} by {segment_col}')
481
- fig.update_layout(yaxis_title="Count")
482
- elif plot_type == "Swarm Plot" and x_axis and y_axis:
483
- 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}')
484
- elif plot_type == "Ridge Plot" and x_axis and y_axis:
485
- fig = px.histogram(df, x=x_axis, color=y_axis, marginal="rug", title=f'Ridge Plot of {x_axis} by {y_axis}')
486
- elif plot_type == "Bubble Plot" and x_axis and y_axis and size_col:
487
- 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}')
488
- elif plot_type == "Density Plot" and x_axis and y_axis:
489
- 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}')
490
- elif plot_type == "Count Plot" and x_axis:
491
- fig = px.bar(df, x=x_axis, color=color_by if color_by != "None" else None, title=f'Count Plot of {x_axis}')
492
- fig.update_layout(yaxis_title="Count")
493
- elif plot_type == "Lollipop Chart" and x_axis and y_axis:
494
- fig = go.Figure()
495
- fig.add_trace(go.Scatter(x=df[x_axis], y=df[y_axis], mode='markers', marker=dict(size=10)))
496
- for i in range(len(df)):
497
- 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')))
498
- fig.update_layout(showlegend=False, title=f'Lollipop Chart of {x_axis} vs {y_axis}')
499
-
500
- if fig:
501
- fig.update_layout(template="plotly_white")
502
- st.plotly_chart(fig, use_container_width=True)
503
- st.session_state.last_plot = {
504
- "type": plot_type,
505
- "x": x_axis,
506
- "y": y_axis,
507
- "z": z_axis,
508
- "color": color_by if color_by != "None" else None,
509
- "data": df[[x_axis, y_axis] + ([z_axis] if z_axis else [])].to_json() if x_axis and y_axis else df[[x_axis]].to_json()
510
- }
511
- else:
512
- st.error("Please provide required inputs for the selected plot type.")
513
- except Exception as e:
514
- st.error(f"Couldn't create visualization: {str(e)}")
515
-
516
- # Chatbot Section
517
- st.markdown("---")
518
- st.subheader("💬 AI Chatbot Assistant (RAG Enabled)")
519
- st.info("Ask me about the app or your data! Try: 'drop columns X, Y', 'scatter plot of X vs Y', or 'analyze plot'")
520
- if "chat_history" not in st.session_state:
521
- st.session_state.chat_history = []
522
-
523
- for message in st.session_state.chat_history:
524
- with st.chat_message(message["role"]):
525
- st.markdown(message["content"])
526
-
527
- user_input = st.chat_input("Ask me anything about the app or your data...")
528
- if user_input:
529
- st.session_state.chat_history.append({"role": "user", "content": user_input})
530
- with st.chat_message("user"):
531
- st.markdown(user_input)
532
- with st.spinner("Processing..."):
533
- dataset_text = st.session_state.get("dataset_text", "")
534
- func, param = parse_command(user_input)
535
- if func:
536
- response = func(param) if param else func(None)
537
- else:
538
- response = get_chatbot_response(user_input, app_mode, dataset_text)
539
- st.session_state.chat_history.append({"role": "assistant", "content": response})
540
- with st.chat_message("assistant"):
541
- st.markdown(response)
 
6
  from ydata_profiling import ProfileReport
7
  from streamlit_pandas_profiling import st_profile_report
8
  import os
9
+ from dotenv import load_dotenv
10
+ from groq import Groq
11
+ from langchain_community.vectorstores import FAISS
12
+ from langchain_community.document_loaders import TextLoader
13
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
14
+ from langchain.embeddings import HuggingFaceEmbeddings
15
  import re
16
  from scipy import stats
17
  from sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder
18
+ import tempfile
 
 
 
 
19
 
20
  # Load environment variables
21
  load_dotenv()
22
 
23
+ # Initialize Groq client
24
+ client = Groq(api_key=os.getenv("GROQ_API_KEY"))
25
+
26
+ # Initialize HuggingFace embeddings for FAISS
27
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
28
+
29
+ # Custom CSS (aligned with previous apps)
30
+ st.markdown("""
31
+ <style>
32
+ :root {
33
+ --primary-blue: #3B82F6;
34
+ --dark-blue: #1E40AF;
35
+ --light-blue: #DBEAFE;
36
+ --medium-grey: #6B7280;
37
+ --light-grey: #F3F4F6;
38
+ --white: #FFFFFF;
39
+ --border-grey: #E5E7EB;
40
+ }
41
+ .stApp {
42
+ background-color: var(--light-grey);
43
+ font-family: 'Inter', sans-serif;
44
+ max-width: 900px;
45
+ margin: 0 auto;
46
+ }
47
+ .header {
48
+ background-color: var(--white);
49
+ border-bottom: 2px solid var(--border-grey);
50
+ padding: 15px;
51
+ border-radius: 12px 12px 0 0;
52
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
53
+ text-align: center;
54
+ }
55
+ .header-title {
56
+ color: var(--dark-blue);
57
+ font-size: 1.5rem;
58
+ font-weight: 700;
59
+ margin: 0;
60
+ }
61
+ .header-subtitle {
62
+ color: var(--medium-grey);
63
+ font-size: 0.9rem;
64
+ margin-top: 5px;
65
+ }
66
+ .sidebar .sidebar-content {
67
+ background-color: var(--white);
68
+ border-radius: 12px;
69
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
70
+ padding: 15px;
71
+ }
72
+ .chat-container {
73
+ background-color: var(--white);
74
+ border-radius: 12px;
75
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
76
+ padding: 15px;
77
+ margin-top: 20px;
78
+ }
79
+ .user-message {
80
+ background-color: var(--primary-blue);
81
+ color: var(--white);
82
+ border-radius: 18px 18px 4px 18px;
83
+ padding: 12px 16px;
84
+ margin-left: auto;
85
+ max-width: 80%;
86
+ margin-bottom: 10px;
87
+ }
88
+ .bot-message {
89
+ background-color: var(--light-grey);
90
+ color: var(--medium-grey);
91
+ border-radius: 18px 18px 18px 4px;
92
+ padding: 12px 16px;
93
+ margin-right: auto;
94
+ max-width: 80%;
95
+ margin-bottom: 10px;
96
+ }
97
+ .footer {
98
+ text-align: center;
99
+ margin-top: 20px;
100
+ color: var(--medium-grey);
101
+ font-size: 0.8rem;
102
+ }
103
+ .tech-badge {
104
+ display: inline-block;
105
+ background-color: var(--light-blue);
106
+ color: var(--dark-blue);
107
+ padding: 4px 8px;
108
+ border-radius: 12px;
109
+ font-size: 0.7rem;
110
+ margin: 0 4px;
111
+ }
112
+ </style>
113
+ """, unsafe_allow_html=True)
114
 
115
+ # Helper Functions
116
+ def enhance_section_title(title):
117
+ st.markdown(f"<h2 style='border-bottom: 2px solid var(--border-grey); padding-bottom: 5px; color: var(--dark-blue);'>{title}</h2>", unsafe_allow_html=True)
118
+
119
+ def update_cleaned_data(df):
120
+ st.session_state.cleaned_data = df
121
+ if 'data_versions' not in st.session_state:
122
+ st.session_state.data_versions = [st.session_state.raw_data.copy()]
123
+ st.session_state.data_versions.append(df.copy())
124
+ st.session_state.dataset_text = convert_df_to_text(df)
125
+ st.success("✅ Action completed successfully!")
126
+ st.rerun()
127
 
128
+ def convert_df_to_text(df):
129
+ """Convert DataFrame to text for vector store and context"""
130
+ text = f"Dataset Summary: {df.shape[0]} rows, {df.shape[1]} columns\n"
131
+ text += f"Missing Values: {df.isna().sum().sum()}\n"
132
+ text += "Columns:\n"
133
+ for col in df.columns:
134
+ text += f"- {col} ({df[col].dtype}): "
135
+ if pd.api.types.is_numeric_dtype(df[col]):
136
+ text += f"Mean={df[col].mean():.2f}, Min={df[col].min()}, Max={df[col].max()}"
137
+ else:
138
+ text += f"Unique={df[col].nunique()}, Top={df[col].mode()[0] if not df[col].mode().empty else 'N/A'}"
139
+ text += f", Missing={df[col].isna().sum()}\n"
140
+ return text
141
+
142
+ def create_vector_store(df_text):
143
+ """Create a FAISS vector store from dataset text"""
144
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as temp_file:
145
+ temp_file.write(df_text)
146
+ temp_path = temp_file.name
147
+
148
+ loader = TextLoader(temp_path)
149
+ documents = loader.load()
150
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
151
+ texts = text_splitter.split_documents(documents)
152
+ vector_store = FAISS.from_documents(texts, embeddings)
153
+ os.unlink(temp_path)
154
+ return vector_store
155
+
156
+ def get_chatbot_response(user_input, app_mode, vector_store=None, model="llama3-70b-8192"):
157
+ """Get response from Groq with vector store context"""
158
  system_prompt = (
159
  "You are an AI assistant in Data-Vision Pro, a data analysis app with RAG capabilities. "
160
+ f"The user is on the '{app_mode}' page:\n"
161
  "- **Data Upload**: Upload CSV/XLSX files, view stats, or generate reports.\n"
162
  "- **Data Cleaning**: Clean data (e.g., handle missing values, encode variables).\n"
163
  "- **EDA**: Visualize data (e.g., scatter plots, histograms).\n"
 
164
  )
165
+
166
+ context = ""
167
+ if vector_store:
168
+ docs = vector_store.similarity_search(user_input, k=3)
169
+ if docs:
170
+ context = "\n\nDataset Context:\n" + "\n".join([f"- {doc.page_content}" for doc in docs])
171
+ system_prompt += f"Use this dataset context to augment your response:\n{context}"
172
  else:
173
  system_prompt += "No dataset is loaded. Assist based on app functionality."
174
+
175
  try:
176
  response = client.chat.completions.create(
177
+ model=model,
178
  messages=[
179
  {"role": "system", "content": system_prompt},
180
  {"role": "user", "content": user_input}
181
  ],
182
+ temperature=0.7,
183
+ max_tokens=1024
184
  )
185
+ return response.choices[0].message.content
186
  except Exception as e:
187
+ return f"Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
188
 
189
+ # Command Functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  def drop_columns(columns):
191
  if 'cleaned_data' in st.session_state:
192
  df = st.session_state.cleaned_data.copy()
 
200
  return "No valid columns found to drop."
201
  return "No dataset loaded."
202
 
 
203
  def generate_scatter_plot(params):
204
  df = st.session_state.cleaned_data
205
  match = re.search(r"([\w\s]+)\s+vs\s+([\w\s]+)", params)
 
222
  return f"Generated histogram of {x_axis}"
223
  return "Invalid column for histogram."
224
 
 
225
  def analyze_plot():
226
  if "last_plot" not in st.session_state:
227
  return "No plot available to analyze."
 
242
  return f"The histogram of {x_col} is {skew_desc} (skewness = {skewness:.2f})."
243
  return "Inference not available for this plot type."
244
 
 
245
  def parse_command(command):
246
  command = command.lower().strip()
247
  if "drop columns" in command or "drop column" in command:
 
255
  return generate_histogram, params
256
  elif "analyze plot" in command:
257
  return lambda x: analyze_plot(), None
258
+ return None, command
259
 
260
  # Dataset Preview Function
261
  def display_dataset_preview():
262
  if 'cleaned_data' in st.session_state:
263
  st.subheader("Current Dataset Preview")
264
  st.dataframe(st.session_state.cleaned_data.head(10), use_container_width=True)
265
+ st.markdown("---")
266
+
267
+ # Main App
268
+ def main():
269
+ # Header
270
+ st.markdown("""
271
+ <div class="header">
272
+ <h1 class="header-title">Data-Vision Pro</h1>
273
+ <div class="header-subtitle">Advanced Data Analysis with Groq Inference</div>
274
+ </div>
275
+ """, unsafe_allow_html=True)
276
+
277
+ # Sidebar Navigation
278
+ with st.sidebar:
279
+ st.markdown("### 🔮 Data-Vision Pro")
280
+ st.markdown("Your AI-powered data analysis suite with RAG.")
281
+ st.markdown("---")
282
+ app_mode = st.selectbox(
283
+ "Navigation",
284
+ ["Data Upload", "Data Cleaning", "EDA"],
285
+ format_func=lambda x: f"📌 {x}"
286
+ )
287
+ model = st.selectbox(
288
+ "Select Groq Model",
289
+ ["llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768", "gemma-7b-it"],
290
+ index=0
291
+ )
292
+ if app_mode == "Data Upload":
293
+ st.info("⬆️ Upload your CSV or XLSX dataset to begin.")
294
+ elif app_mode == "Data Cleaning":
295
+ st.info("🧹 Clean and preprocess your data.")
296
+ elif app_mode == "EDA":
297
+ st.info("🔍 Explore your data visually.")
298
+
299
+ if 'cleaned_data' in st.session_state:
300
+ csv = st.session_state.cleaned_data.to_csv(index=False)
301
+ st.download_button(
302
+ label="Download Cleaned Data",
303
+ data=csv,
304
+ file_name='cleaned_data.csv',
305
+ mime='text/csv',
306
+ )
307
+ st.markdown("---")
308
+ st.markdown("Built with <span class='tech-badge'>Streamlit</span> + <span class='tech-badge'>Groq</span>", unsafe_allow_html=True)
309
+
310
+ # Initialize Session State
311
+ if 'vector_store' not in st.session_state:
312
+ st.session_state.vector_store = None
313
+ if 'chat_history' not in st.session_state:
314
+ st.session_state.chat_history = []
315
+
316
+ # Display Dataset Preview
317
+ display_dataset_preview()
318
+
319
+ # App Pages
320
  if app_mode == "Data Upload":
321
+ st.header("📤 Data Upload & Profiling")
322
+ uploaded_file = st.file_uploader("Choose a file", type=["csv", "xlsx"], key="file_uploader")
323
+ if uploaded_file:
324
+ st.session_state.pop('raw_data', None)
325
+ st.session_state.pop('cleaned_data', None)
326
+ st.session_state.pop('data_versions', None)
327
+ try:
328
+ if uploaded_file.name.endswith('.csv'):
329
+ df = pd.read_csv(uploaded_file)
330
+ else:
331
+ df = pd.read_excel(uploaded_file)
332
+ if df.empty:
333
+ st.error("Uploaded file is empty.")
334
+ st.stop()
335
+ st.session_state.raw_data = df
336
+ st.session_state.cleaned_data = df.copy()
337
+ st.session_state.dataset_text = convert_df_to_text(df)
338
+ st.session_state.vector_store = create_vector_store(st.session_state.dataset_text)
339
+ if 'data_versions' not in st.session_state:
340
+ st.session_state.data_versions = [df.copy()]
341
+ col1, col2, col3 = st.columns(3)
342
+ with col1: st.metric("Rows", df.shape[0])
343
+ with col2: st.metric("Columns", df.shape[1])
344
+ with col3: st.metric("Missing Values", df.isna().sum().sum())
345
+ if st.checkbox("Show Data Preview"):
346
+ st.dataframe(df.head(10), use_container_width=True)
347
+ if st.button("Generate Full Profile Report"):
348
+ with st.spinner("Generating report..."):
349
+ pr = ProfileReport(df, explorative=True)
350
+ st_profile_report(pr)
351
+ st.success("✅ Data loaded successfully!")
352
+ except Exception as e:
353
+ st.error(f"An error occurred: {str(e)}")
354
+
355
  elif app_mode == "Data Cleaning":
356
+ st.header("🧹 Smart Data Cleaning")
357
+ if 'raw_data' not in st.session_state:
358
+ st.warning("Please upload data first in the Data Upload section.")
359
+ st.stop()
360
+ if 'cleaned_data' not in st.session_state:
361
+ st.session_state.cleaned_data = st.session_state.raw_data.copy()
362
+ df = st.session_state.cleaned_data.copy()
 
 
 
363
 
364
+ enhance_section_title("📊 Data Health Dashboard")
365
+ with st.expander("Explore Data Health Metrics", expanded=True):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  col1, col2, col3 = st.columns(3)
367
+ with col1: st.metric("Columns", len(df.columns))
368
+ with col2: st.metric("Rows", len(df))
369
  with col3: st.metric("Missing Values", df.isna().sum().sum())
370
+ if st.button("Generate Detailed Health Report"):
 
 
371
  with st.spinner("Generating report..."):
372
+ profile = ProfileReport(df, minimal=True)
373
+ st_profile_report(profile)
374
+ if 'data_versions' in st.session_state and len(st.session_state.data_versions) > 1:
375
+ if st.button("Undo Last Action"):
376
+ st.session_state.data_versions.pop()
377
+ st.session_state.cleaned_data = st.session_state.data_versions[-1].copy()
378
+ st.session_state.dataset_text = convert_df_to_text(st.session_state.cleaned_data)
379
+ st.session_state.vector_store = create_vector_store(st.session_state.dataset_text)
380
+ st.rerun()
381
+
382
+ with st.expander("🛠️ Data Cleaning Operations", expanded=True):
383
+ enhance_section_title("🔍 Missing Values Treatment")
384
+ missing_cols = df.columns[df.isna().any()].tolist()
385
+ if missing_cols:
386
+ cols = st.multiselect("Select columns with missing values", missing_cols)
387
+ method = st.selectbox("Choose imputation method", [
388
+ "Drop Missing Values", "Fill with Mean/Median", "Fill with Custom Value", "Forward Fill", "Backward Fill"
389
+ ])
390
+ if method == "Fill with Custom Value":
391
+ custom_val = st.text_input("Enter custom value:")
392
+ if st.button("Apply Missing Value Treatment"):
393
+ new_df = df.copy()
394
+ if method == "Drop Missing Values":
395
+ new_df = new_df.dropna(subset=cols)
396
+ elif method == "Fill with Mean/Median":
397
+ for col in cols:
398
+ if pd.api.types.is_numeric_dtype(new_df[col]):
399
+ new_df[col] = new_df[col].fillna(new_df[col].median())
400
+ else:
401
+ new_df[col] = new_df[col].fillna(new_df[col].mode()[0])
402
+ elif method == "Fill with Custom Value" and custom_val:
403
+ new_df[cols] = new_df[cols].fillna(custom_val)
404
+ elif method == "Forward Fill":
405
+ new_df[cols] = new_df[cols].ffill()
406
+ elif method == "Backward Fill":
407
+ new_df[cols] = new_df[cols].bfill()
408
+ update_cleaned_data(new_df)
409
+ else:
410
+ st.success(" No missing values detected!")
411
+
412
+ enhance_section_title("🔄 Data Type Conversion")
413
+ col_to_convert = st.selectbox("Select column to convert", df.columns)
414
+ new_type = st.selectbox("Select new data type", ["String", "Integer", "Float", "Boolean", "Datetime"])
415
+ if new_type == "Datetime":
416
+ date_format = st.text_input("Enter date format (e.g., %Y-%m-%d):", "%Y-%m-%d")
417
+ if st.button("Convert Data Type"):
418
  new_df = df.copy()
419
+ if new_type == "String":
420
+ new_df[col_to_convert] = new_df[col_to_convert].astype(str)
421
+ elif new_type == "Integer":
422
+ new_df[col_to_convert] = pd.to_numeric(new_df[col_to_convert], errors='coerce').astype('Int64')
423
+ elif new_type == "Float":
424
+ new_df[col_to_convert] = pd.to_numeric(new_df[col_to_convert], errors='coerce')
425
+ elif new_type == "Boolean":
426
+ new_df[col_to_convert] = new_df[col_to_convert].astype(bool)
427
+ elif new_type == "Datetime":
428
+ new_df[col_to_convert] = pd.to_datetime(new_df[col_to_convert], format=date_format, errors='coerce')
 
 
 
 
429
  update_cleaned_data(new_df)
430
+
431
+ enhance_section_title("🗑️ Drop Columns")
432
+ columns_to_drop = st.multiselect("Select columns to remove", df.columns)
433
+ if columns_to_drop and st.button("Confirm Column Removal"):
434
+ new_df = df.copy()
435
+ new_df = new_df.drop(columns=columns_to_drop)
436
+ update_cleaned_data(new_df)
437
+
438
+ enhance_section_title("🔢 Encoding Options")
439
+ encoding_method = st.radio("Choose encoding method", ("Label Encoding", "One-Hot Encoding"))
440
+ data_to_encode = st.multiselect("Select columns to encode", df.select_dtypes(include='object').columns)
441
+ if data_to_encode and st.button("Apply Encoding"):
442
+ new_df = df.copy()
443
+ if encoding_method == "Label Encoding":
444
+ for col in data_to_encode:
445
+ le = LabelEncoder()
446
+ new_df[col] = le.fit_transform(new_df[col].astype(str))
447
+ elif encoding_method == "One-Hot Encoding":
448
+ new_df = pd.get_dummies(new_df, columns=data_to_encode, drop_first=True, dtype=int)
449
+ update_cleaned_data(new_df)
450
+
451
+ enhance_section_title("📏 StandardScaler")
452
+ scale_cols = st.multiselect("Select numerical columns to scale", df.select_dtypes(include=np.number).columns)
453
+ if scale_cols and st.button("Apply StandardScaler"):
454
+ new_df = df.copy()
455
+ scaler = StandardScaler()
456
+ new_df[scale_cols] = scaler.fit_transform(new_df[scale_cols])
457
+ update_cleaned_data(new_df)
458
+
459
+ elif app_mode == "EDA":
460
+ st.header("🔍 Interactive Data Explorer")
461
+ if 'cleaned_data' not in st.session_state:
462
+ st.warning("Please upload and clean data first.")
463
+ st.stop()
464
+ df = st.session_state.cleaned_data.copy()
465
+
466
+ enhance_section_title("Dataset Overview")
467
+ with st.container():
468
+ col1, col2, col3, col4 = st.columns(4)
469
+ col1.metric("Total Rows", df.shape[0])
470
+ col2.metric("Total Columns", df.shape[1])
471
+ missing_percentage = df.isna().sum().sum() / df.size * 100
472
+ col3.metric("Missing Values", f"{df.isna().sum().sum()} ({missing_percentage:.1f}%)")
473
+ col4.metric("Duplicates", df.duplicated().sum())
474
+
475
+ tab1, tab2, tab3 = st.tabs(["Quick Preview", "Column Types", "Missing Matrix"])
476
+ with tab1:
477
+ st.write("First few rows of the dataset:")
478
+ st.dataframe(df.head(), use_container_width=True)
479
+ with tab2:
480
+ st.write("Column Data Types:")
481
+ type_counts = df.dtypes.value_counts().reset_index()
482
+ type_counts.columns = ['Type', 'Count']
483
+ st.dataframe(type_counts, use_container_width=True)
484
+ with tab3:
485
+ st.write("Missing Values Matrix:")
486
+ fig_missing = px.imshow(df.isna(), color_continuous_scale=['#e0e0e0', '#66c2a5'])
487
+ fig_missing.update_layout(coloraxis_colorscale=[[0, 'lightgrey'], [1, '#FF4B4B']])
488
+ st.plotly_chart(fig_missing, use_container_width=True)
489
+
490
+ enhance_section_title("Interactive Visualization Builder")
491
+ with st.container():
492
+ col1, col2 = st.columns([1, 3])
493
+ with col1:
494
+ plot_type = st.selectbox("Choose visualization type", [
495
+ "Scatter Plot", "Histogram", "Box Plot", "Line Chart", "Bar Chart", "Correlation Matrix"
496
+ ])
497
+ x_axis = st.selectbox("X-axis", df.columns) if plot_type != "Correlation Matrix" else None
498
+ y_axis = st.selectbox("Y-axis", df.columns) if plot_type in ["Scatter Plot", "Box Plot", "Line Chart"] else None
499
+ color_by = st.selectbox("Color encoding", ["None"] + df.columns.tolist(), format_func=lambda x: "No color" if x == "None" else x) if plot_type != "Correlation Matrix" else None
500
+
501
+ with col2:
502
+ try:
503
+ fig = None
504
+ if plot_type == "Scatter Plot" and x_axis and y_axis:
505
+ fig = px.scatter(df, x=x_axis, y=y_axis, color=color_by if color_by != "None" else None, title=f'Scatter Plot of {x_axis} vs {y_axis}')
506
+ elif plot_type == "Histogram" and x_axis:
507
+ fig = px.histogram(df, x=x_axis, color=color_by if color_by != "None" else None, nbins=30, title=f'Histogram of {x_axis}')
508
+ elif plot_type == "Box Plot" and x_axis and y_axis:
509
+ 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}')
510
+ elif plot_type == "Line Chart" and x_axis and y_axis:
511
+ 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}')
512
+ elif plot_type == "Bar Chart" and x_axis:
513
+ fig = px.bar(df, x=x_axis, color=color_by if color_by != "None" else None, title=f'Bar Chart of {x_axis}')
514
+ elif plot_type == "Correlation Matrix":
515
+ numeric_df = df.select_dtypes(include=np.number)
516
+ if len(numeric_df.columns) > 1:
517
+ corr = numeric_df.corr()
518
+ fig = px.imshow(corr, text_auto=True, color_continuous_scale='RdBu_r', zmin=-1, zmax=1, title='Correlation Matrix')
519
+
520
+ if fig:
521
+ fig.update_layout(template="plotly_white")
522
+ st.plotly_chart(fig, use_container_width=True)
523
+ st.session_state.last_plot = {
524
+ "type": plot_type,
525
+ "x": x_axis,
526
+ "y": y_axis,
527
+ "data": df[[x_axis, y_axis]].to_json() if y_axis else df[[x_axis]].to_json()
528
+ }
529
+ else:
530
+ st.error("Please provide required inputs for the selected plot type.")
531
+ except Exception as e:
532
+ st.error(f"Couldn't create visualization: {str(e)}")
533
+
534
+ # Chatbot Section
535
+ st.markdown("---")
536
+ st.markdown('<div class="chat-container">', unsafe_allow_html=True)
537
+ st.subheader("💬 AI Chatbot Assistant (RAG Enabled)")
538
+ st.info("Ask about your data or app features! Try: 'drop columns X, Y', 'scatter plot of X vs Y', 'analyze plot'")
539
+
540
+ for message in st.session_state.chat_history:
541
+ with st.chat_message(message["role"]):
542
+ st.markdown(f'<div class="{message["role"]}-message">{message["content"]}</div>', unsafe_allow_html=True)
543
+
544
+ user_input = st.chat_input("Ask me anything...")
545
+ if user_input:
546
+ st.session_state.chat_history.append({"role": "user", "content": user_input})
547
+ with st.chat_message("user"):
548
+ st.markdown(f'<div class="user-message">{user_input}</div>', unsafe_allow_html=True)
549
+ with st.spinner("Processing..."):
550
+ func, param = parse_command(user_input)
551
+ if func:
552
+ response = func(param) if param else func(None)
553
+ else:
554
+ response = get_chatbot_response(user_input, app_mode, st.session_state.vector_store, model)
555
+ st.session_state.chat_history.append({"role": "assistant", "content": response})
556
+ with st.chat_message("assistant"):
557
+ st.markdown(f'<div class="bot-message">{response}</div>', unsafe_allow_html=True)
558
+
559
+ st.markdown('</div>', unsafe_allow_html=True)
560
+
561
+ # Footer
562
+ st.markdown("""
563
+ <div class="footer">
564
+ <div>Built with <span class="tech-badge">Streamlit</span> + <span class="tech-badge">Groq</span> + <span class="tech-badge">LangChain</span> + <span class="tech-badge">FAISS</span></div>
565
+ <div style="margin-top: 8px;">Fast inference for data insights</div>
566
+ </div>
567
+ """, unsafe_allow_html=True)
568
+
569
+ if __name__ == "__main__":
570
+ st.set_page_config(page_title="Data-Vision Pro", layout="wide")
571
+ main()