import os import pandas as pd import numpy as np import streamlit as st import plotly.express as px import plotly.figure_factory as ff from dotenv import load_dotenv from huggingface_hub import InferenceClient, login import google.generativeai as genai from io import StringIO # ====================================================== # โš™๏ธ APP CONFIGURATION # ====================================================== st.set_page_config(page_title="๐Ÿ“Š Smart Data Analyst Pro", layout="wide") st.title("๐Ÿ“Š Smart Data Analyst Pro") st.caption("AI that cleans, analyzes, and visualizes your data โ€” Hugging Face + Gemini compatible.") # ====================================================== # ๐Ÿ” Load Environment Variables # ====================================================== load_dotenv() HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_API_KEY") GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") if not HF_TOKEN: st.error("โŒ Missing HF_TOKEN. Please set it in your .env file.") else: login(token=HF_TOKEN) if GEMINI_API_KEY: genai.api_key = GEMINI_API_KEY else: st.warning("โš ๏ธ Gemini API key missing. Gemini 2.5 Flash will not work.") # ====================================================== # ๐Ÿง  MODEL SETUP # ====================================================== with st.sidebar: st.header("โš™๏ธ Model Settings") CLEANER_MODEL = st.selectbox( "Select Cleaner Model:", [ "Qwen/Qwen2.5-Coder-14B", "meta-llama/Meta-Llama-3-8B-Instruct", "microsoft/Phi-3-mini-4k-instruct", "mistralai/Mistral-7B-Instruct-v0.3" ], index=0 ) ANALYST_MODEL = st.selectbox( "Select Analysis Model:", [ "Qwen/Qwen2.5-14B-Instruct", "Gemini 2.5 Flash (Google)", "mistralai/Mistral-7B-Instruct-v0.3", "HuggingFaceH4/zephyr-7b-beta" ], index=0 ) temperature = st.slider("Temperature", 0.0, 1.0, 0.3) max_tokens = st.slider("Max Tokens", 128, 4096, 1024) # Initialize HF clients hf_cleaner_client = InferenceClient(model=CLEANER_MODEL, token=HF_TOKEN) hf_analyst_client = None if ANALYST_MODEL != "Gemini 2.5 Flash (Google)": hf_analyst_client = InferenceClient(model=ANALYST_MODEL, token=HF_TOKEN) # ====================================================== # ๐Ÿงฉ SAFE GENERATION FUNCTION # ====================================================== def safe_hf_generate(client, prompt, temperature=0.3, max_tokens=512): try: resp = client.text_generation( prompt, temperature=temperature, max_new_tokens=max_tokens, return_full_text=False, ) return resp.strip() except Exception as e: if "Supported task: conversational" in str(e) or "not supported" in str(e): chat_resp = client.chat_completion( messages=[{"role": "user", "content": prompt}], max_tokens=max_tokens, temperature=temperature, ) return chat_resp["choices"][0]["message"]["content"].strip() else: raise e # ====================================================== # ๐Ÿงฉ SMART DATA CLEANING # ====================================================== def fallback_clean(df: pd.DataFrame) -> pd.DataFrame: df = df.copy() df.dropna(axis=1, how="all", inplace=True) df.columns = [c.strip().replace(" ", "_").lower() for c in df.columns] for col in df.columns: if df[col].dtype == "O": if not df[col].mode().empty: df[col].fillna(df[col].mode()[0], inplace=True) else: df[col].fillna("Unknown", inplace=True) else: df[col].fillna(df[col].median(), inplace=True) df.drop_duplicates(inplace=True) return df def ai_clean_dataset(df: pd.DataFrame) -> (pd.DataFrame, str): """Return cleaned dataset and a message if cleaning failed.""" max_allowed_rows = 2000 if len(df) > max_allowed_rows: return df, f"โš ๏ธ Dataset too large for AI cleaning (>{max_allowed_rows} rows). Using original dataset." csv_text = df.to_csv(index=False) prompt = f""" You are a professional data cleaning assistant. Clean and standardize the dataset below dynamically: 1. Handle missing values 2. Fix column name inconsistencies 3. Convert data types (dates, numbers, categories) 4. Remove irrelevant or duplicate rows Return ONLY a valid CSV text (no markdown, no explanations). Dataset: {csv_text} """ try: cleaned_str = safe_hf_generate(hf_cleaner_client, prompt, temperature=0.1, max_tokens=4096) cleaned_str = cleaned_str.replace("```csv", "").replace("```", "").replace("###", "").strip() cleaned_df = pd.read_csv(StringIO(cleaned_str), on_bad_lines="skip") cleaned_df.columns = [c.strip().replace(" ", "_").lower() for c in cleaned_df.columns] return cleaned_df, "" except Exception as e: return df, f"โš ๏ธ AI cleaning failed: {e}. Using original dataset for analysis." # ====================================================== # ๐Ÿงฉ DATA ANALYSIS # ====================================================== def query_analysis_model(df: pd.DataFrame, user_query: str, dataset_name: str) -> str: csv_text = df.to_csv(index=False) prompt = f""" You are a professional data analyst. Analyze the dataset '{dataset_name}' and answer the user's question. --- FULL DATA --- {csv_text} --- USER QUESTION --- {user_query} Respond with: 1. Key insights and patterns 2. Quantitative findings 3. Notable relationships or anomalies 4. Data-driven recommendations """ try: if ANALYST_MODEL == "Gemini 2.5 Flash (Google)": if GEMINI_API_KEY is None: return "โš ๏ธ Gemini API key missing." response = genai.generate_text( model="gemini-2.5-flash", prompt=prompt, temperature=temperature, max_output_tokens=max_tokens ) return getattr(response, "candidates", [{"content": "No response from Gemini."}])[0]["content"] else: return safe_hf_generate(hf_analyst_client, prompt, temperature=temperature, max_tokens=max_tokens) except Exception as e: return f"โš ๏ธ Analysis failed: {e}" # ====================================================== # ๐Ÿš€ MAIN APP LOGIC # ====================================================== uploaded = st.file_uploader("๐Ÿ“Ž Upload CSV or Excel file", type=["csv", "xlsx"]) if uploaded: df = pd.read_csv(uploaded) if uploaded.name.endswith(".csv") else pd.read_excel(uploaded) with st.spinner("๐Ÿงผ AI Cleaning your dataset..."): cleaned_df, cleaning_msg = ai_clean_dataset(df) if cleaning_msg: st.warning(cleaning_msg) st.info("๐Ÿ’ก Note: For AI cleaning to work best, datasets should ideally be under 2000 rows.") st.subheader("โœ… Dataset Preview") st.dataframe(cleaned_df.head(), use_container_width=True) # ================== Quick Visualizations ================== with st.expander("๐Ÿ“ˆ Quick Visualizations", expanded=True): numeric_cols = cleaned_df.select_dtypes(include="number").columns.tolist() categorical_cols = cleaned_df.select_dtypes(exclude="number").columns.tolist() viz_type = st.selectbox( "Visualization Type", ["Scatter Plot", "Histogram", "Box Plot", "Correlation Heatmap", "Categorical Count"] ) if viz_type == "Scatter Plot" and len(numeric_cols) >= 2: x = st.selectbox("X-axis", numeric_cols) y = st.selectbox("Y-axis", numeric_cols, index=min(1, len(numeric_cols)-1)) color = st.selectbox("Color", ["None"] + categorical_cols) fig = px.scatter(cleaned_df, x=x, y=y, color=None if color=="None" else color) st.plotly_chart(fig, use_container_width=True) elif viz_type == "Histogram" and numeric_cols: col = st.selectbox("Column", numeric_cols) fig = px.histogram(cleaned_df, x=col, nbins=30) st.plotly_chart(fig, use_container_width=True) elif viz_type == "Box Plot" and numeric_cols: col = st.selectbox("Column", numeric_cols) fig = px.box(cleaned_df, y=col) st.plotly_chart(fig, use_container_width=True) elif viz_type == "Correlation Heatmap" and len(numeric_cols) > 1: corr = cleaned_df[numeric_cols].corr() fig = ff.create_annotated_heatmap( z=corr.values, x=list(corr.columns), y=list(corr.index), annotation_text=corr.round(2).values, showscale=True ) st.plotly_chart(fig, use_container_width=True) elif viz_type == "Categorical Count" and categorical_cols: cat = st.selectbox("Category", categorical_cols) fig = px.bar(cleaned_df[cat].value_counts().reset_index(), x="index", y=cat) st.plotly_chart(fig, use_container_width=True) else: st.warning("โš ๏ธ Not enough columns for this visualization type.") # ================== AI Analysis ================== st.subheader("๐Ÿ’ฌ Ask AI About Your Data") user_query = st.text_area("Enter your question:", placeholder="e.g. What factors influence sales the most?") if st.button("Analyze with AI", use_container_width=True) and user_query: with st.spinner("๐Ÿค– Interpreting data..."): result = query_analysis_model(cleaned_df, user_query, uploaded.name) st.markdown("### ๐Ÿ’ก Insights") st.markdown(result) else: st.info("๐Ÿ“ฅ Upload a dataset to begin smart analysis.")