Spaces:
Sleeping
Sleeping
| 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.") | |