# Experiment 1: Data Pre-processing and Exploratory Data Analysis (EDA)
# Requirements: pip install numpy pandas matplotlib seaborn scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import LabelEncoder, MinMaxScaler

# ==========================================
# STEP 1: Loading the Dataset
# ==========================================
# Raw retail customer dataset (from the problem statement).
# To use your own data instead, uncomment the next line:
# CHANGE THIS PATH TO YOUR DATASET
# df = pd.read_csv("your_dataset.csv")
df = pd.DataFrame({
    'CustomerID': [101, 102, 103, 104, 105, 106, 107],
    'Age': [23, 45, np.nan, 31, 22, 54, 38],
    'Annual_Income_k': [50, 85, 62, np.nan, 45, 120, 350],
    'City': ['New York', 'Paris', 'New York', 'London', np.nan, 'London', 'Paris']
})

print("--- Raw Dataset ---")
print(df)

# ==========================================
# STEP 2: Data Inspection & Handling Missing Values
# ==========================================
print("\n--- Dataset Info ---")
df.info()

print("\n--- Missing Values per Column ---")
print(df.isnull().sum())

# Numerical columns -> Median imputation (robust to outliers)
for col in ['Age', 'Annual_Income_k']:
    df[col] = df[col].fillna(df[col].median())

# Categorical column -> Mode imputation
df['City'] = df['City'].fillna(df['City'].mode()[0])

print("\n--- Dataset after Imputation ---")
print(df)

# ==========================================
# STEP 3: Outlier Detection & Treatment (IQR Capping)
# ==========================================
Q1 = df['Annual_Income_k'].quantile(0.25)
Q3 = df['Annual_Income_k'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

print(f"\nIQR Bounds for Annual_Income_k: [{lower_bound:.2f}, {upper_bound:.2f}]")
outliers = df[(df['Annual_Income_k'] < lower_bound) | (df['Annual_Income_k'] > upper_bound)]
print("Detected Outliers:")
print(outliers)

# Boxplot before capping
plt.figure(figsize=(6, 4))
sns.boxplot(x=df['Annual_Income_k'])
plt.title('Annual Income (Before Outlier Capping)')
plt.show()

# Cap values outside the IQR bounds
df['Annual_Income_k'] = df['Annual_Income_k'].clip(lower=lower_bound, upper=upper_bound)

print("\n--- Dataset after Outlier Capping ---")
print(df)

# ==========================================
# STEP 4: Categorical Encoding & Feature Scaling
# ==========================================
# Encode City names into integer identifiers
le = LabelEncoder()
df['City_Encoded'] = le.fit_transform(df['City'])
print("\nCity Encoding:", dict(zip(le.classes_, le.transform(le.classes_))))

# Normalize Age and Income to the range [0, 1]
scaler = MinMaxScaler()
df[['Age_Scaled', 'Income_Scaled']] = scaler.fit_transform(df[['Age', 'Annual_Income_k']])

print("\n--- Final Pre-processed Dataset ---")
print(df)

# ==========================================
# STEP 5: Exploratory Data Analysis (EDA) - Heatmaps & Histograms
# ==========================================
# Histograms: distribution of Age and Income
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.histplot(df['Age'], bins=5, kde=True, ax=axes[0], color='skyblue')
axes[0].set_title('Age Distribution')
sns.histplot(df['Annual_Income_k'], bins=5, kde=True, ax=axes[1], color='salmon')
axes[1].set_title('Annual Income Distribution (k)')
plt.tight_layout()
plt.show()

# Scatter plot: demographic clusters by City
plt.figure(figsize=(6, 4))
sns.scatterplot(data=df, x='Age', y='Annual_Income_k', hue='City', s=100)
plt.title('Age vs Annual Income by City')
plt.show()

# Correlation heatmap
numeric_cols = ['Age', 'Annual_Income_k', 'City_Encoded']
plt.figure(figsize=(6, 5))
sns.heatmap(df[numeric_cols].corr(), annot=True, cmap='coolwarm', fmt='.2f')
plt.title('Feature Correlation Heatmap')
plt.show()
