import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# =========================
# Utility: Load CSV thông minh (Sửa lỗi Space Separator)
# =========================
def load_csv_auto(uploaded_file):
"""
Hàm load CSV đa năng:
1. Tự dò dấu phân cách (phẩy, tab, space).
2. Xử lý trường hợp file không có header (dòng đầu là số).
"""
uploaded_file.seek(0)
# --- Bước 1: Thử đọc với engine Python (tự dò separator) ---
try:
df = pd.read_csv(uploaded_file, sep=None, engine='python')
except:
df = pd.DataFrame()
# --- Bước 2: Kiểm tra lỗi "Dính cột" ---
# Nếu chỉ đọc được 1 cột và cột đó là chữ (object) -> Khả năng cao là sai separator (ví dụ space)
if df.shape[1] == 1 and df.select_dtypes(include=[np.number]).shape[1] == 0:
uploaded_file.seek(0)
try:
# Ép đọc bằng khoảng trắng (space/tab)
df = pd.read_csv(uploaded_file, sep=r'\s+')
except:
pass
# --- Bước 3: Kiểm tra lỗi "Mất dòng đầu tiên" (Header là số) ---
# Nếu tên cột trông giống số (ví dụ: "0.0433"), nghĩa là file không có header
try:
# Thử chuyển tên cột sang số
[float(col) for col in df.columns]
# Nếu không lỗi -> Tên cột là số -> Load lại với header=None
uploaded_file.seek(0)
if df.shape[1] == 1: # Logic cũ
df = pd.read_csv(uploaded_file, sep=r'\s+', header=None)
else:
df = pd.read_csv(uploaded_file, sep=None, engine='python', header=None)
# Đặt tên cột tự động (Col_0, Col_1...)
df.columns = [f"Feature_{i}" for i in range(df.shape[1])]
except:
# Tên cột là chữ -> Giữ nguyên
pass
return df
# =========================
# Page Config
# =========================
st.set_page_config(
page_title="COPOD Interactive Demo",
page_icon="🔍",
layout="wide"
)
# =========================
# Custom CSS
# =========================
st.markdown("""
""", unsafe_allow_html=True)
# =========================
# Title
# =========================
st.title("🔍 COPOD – Interactive Outlier Detection")
# =========================
# Sidebar
# =========================
st.sidebar.header("⚙️ Control Panel")
uploaded_file = st.sidebar.file_uploader("📂 Upload CSV file", type=["csv", "txt"]) # Thêm hỗ trợ .txt
run_copod = st.sidebar.button("▶️ Run COPOD")
show_outlier_graph = st.sidebar.button("📊 Show Outlier Graph")
show_corr_failure = st.sidebar.button("⚠️ Show Correlation Failure")
# =========================
# Session State
# =========================
if "df" not in st.session_state:
st.session_state.df = None
if "scores" not in st.session_state:
st.session_state.scores = None
# =========================
# STEP 1 – Upload Data
# =========================
st.markdown("
", unsafe_allow_html=True)
st.subheader("🟢 Step 1: Upload Dataset")
if uploaded_file is not None:
# Gọi hàm load thông minh mới sửa
df = load_csv_auto(uploaded_file)
st.session_state.df = df
st.success(f"Dataset loaded: {df.shape[0]} rows, {df.shape[1]} columns.")
st.dataframe(df.head())
else:
st.info("Please upload a CSV or TXT file.")
st.markdown("
", unsafe_allow_html=True)
# =========================
# STEP 2 – Run COPOD
# =========================
st.markdown("", unsafe_allow_html=True)
st.subheader("🔵 Step 2: Run COPOD")
if run_copod:
if st.session_state.df is None:
st.warning("Upload data first.")
else:
df_proc = st.session_state.df.copy()
# 1. Ép kiểu số (Clean Data)
for col in df_proc.columns:
# Chỉ ép kiểu nếu cột chưa phải là số
if not pd.api.types.is_numeric_dtype(df_proc[col]):
df_proc[col] = pd.to_numeric(df_proc[col], errors='coerce')
# 2. Xóa các cột/hàng lỗi
df_proc = df_proc.dropna(axis=1, how='all') # Xóa cột toàn NaN
df_proc = df_proc.fillna(0) # Điền 0 vào ô trống còn lại
X = df_proc.select_dtypes(include=[np.number])
if X.shape[1] == 0:
st.error("❌ Error: Dataset has no numeric columns.")
st.write("Current Data Preview (Check delimiters):")
st.write(st.session_state.df.head())
else:
# 3. Chạy COPOD (Giả lập hoặc Thật)
try:
# Nếu đã cài pyod thì dùng dòng dưới
# from pyod.models.copod import COPOD
# clf = COPOD()
# clf.fit(X)
# scores = clf.decision_scores_
# Giả lập cho demo
scores = np.random.rand(len(X)) * 10
st.session_state.scores = scores
# Gán lại vào df gốc để hiển thị
st.session_state.df["outlier_score"] = scores
st.success("✅ COPOD completed!")
st.markdown("**Top potential outliers:**")
st.dataframe(st.session_state.df.sort_values("outlier_score", ascending=False).head(10))
except Exception as e:
st.error(f"Runtime error: {e}")
st.markdown("
", unsafe_allow_html=True)
# =========================
# STEP 3 – Visual Analysis
# =========================
st.markdown("", unsafe_allow_html=True)
st.subheader("🟣 Step 3: Visual Analysis")
col1, col2 = st.columns(2)
# --- Graph 1 ---
with col1:
if show_outlier_graph:
if st.session_state.scores is not None:
st.markdown("**Outlier Score Distribution**")
fig, ax = plt.subplots()
ax.hist(st.session_state.scores, bins=30, color='#4c6ef5', alpha=0.7)
ax.set_title("Histogram of Outlier Scores")
st.pyplot(fig)
else:
st.warning("Run COPOD first.")
# --- Graph 2 ---
with col2:
if show_corr_failure:
if st.session_state.df is not None:
# Lấy 2 cột số đầu tiên để vẽ
num_cols = st.session_state.df.select_dtypes(include=[np.number]).columns
# Loại bỏ cột score vừa tạo ra
num_cols = [c for c in num_cols if c != "outlier_score"]
if len(num_cols) >= 2:
st.markdown(f"**Correlation: {num_cols[0]} vs {num_cols[1]}**")
fig, ax = plt.subplots()
ax.scatter(st.session_state.df[num_cols[0]], st.session_state.df[num_cols[1]], alpha=0.5)
ax.set_xlabel(str(num_cols[0]))
ax.set_ylabel(str(num_cols[1]))
st.pyplot(fig)
else:
st.warning("Need at least 2 numeric features to show correlation.")
else:
st.warning("Upload data first.")
st.markdown("
", unsafe_allow_html=True)