# Experiment 5: K-Nearest Neighbors (KNN) Classifier for Credit Risk Rating
# Requirements: pip install numpy pandas matplotlib seaborn scikit-learn

# Import essential libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score, roc_auc_score

# 1. Generate Synthetic Dataset for Credit Risk Rating
np.random.seed(42)
n_samples = 1000

data = {
    'Debt_to_Income_Ratio': np.random.uniform(0.1, 0.8, n_samples),
    'Credit_Score': np.random.randint(300, 850, n_samples),
    'Annual_Income': np.random.uniform(20000, 150000, n_samples),
    'Total_Assets': np.random.uniform(5000, 500000, n_samples)
}
df = pd.DataFrame(data)

# Generate Multi-class Target Variable: Credit_Risk_Rating (0=Low, 1=Moderate, 2=High)
risk_score = (df['Debt_to_Income_Ratio'] * 3.0) - (df['Credit_Score'] / 250.0) - (df['Annual_Income'] / 100000.0)
df['Credit_Risk_Rating'] = pd.qcut(risk_score, q=3, labels=[0, 1, 2]).astype(int)

# 2. Train-Test Split and Feature Scaling
X = df[['Debt_to_Income_Ratio', 'Credit_Score', 'Annual_Income', 'Total_Assets']]
y = df['Credit_Risk_Rating']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42, stratify=y)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 3. Hyperparameter Tuning: Finding Optimal K
k_range = range(1, 26)
cv_scores = []

for k in k_range:
    knn = KNeighborsClassifier(n_neighbors=k, metric='minkowski', p=2)
    scores = cross_val_score(knn, X_train_scaled, y_train, cv=5, scoring='accuracy')
    cv_scores.append(scores.mean())

optimal_k = k_range[np.argmax(cv_scores)]
print(f'Optimal Neighborhood Parameter (K): {optimal_k}')

# 4. Train Final KNN Model with Optimal K
knn_optimal = KNeighborsClassifier(n_neighbors=optimal_k, weights='distance')
knn_optimal.fit(X_train_scaled, y_train)

# 5. Prediction and Evaluation
y_pred = knn_optimal.predict(X_test_scaled)
y_probs = knn_optimal.predict_proba(X_test_scaled)

print('\n--- Confusion Matrix ---')
print(confusion_matrix(y_test, y_pred))

print('\n--- Classification Report ---')
print(classification_report(y_test, y_pred, target_names=['Low Risk', 'Moderate Risk', 'High Risk']))

auc_score = roc_auc_score(y_test, y_probs, multi_class='ovr')
print(f'Multi-class ROC-AUC Score (OvR): {auc_score:.4f}')
