import pandas as pd
import numpy as np  # Don't forget to import numpy
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import confusion_matrix

# Load the Fashion MNIST dataset
mnist_df = pd.read_csv('fashion-mnist_test.csv')

# Extract labels and features from the dataset
labels = mnist_df['label'].values
features = mnist_df.drop('label', axis=1).values

# Normalize the pixel values to the range [0, 1]
features = features / 255.0

# Display information about the dataset
print("Number of samples:", features.shape[0])
print("Number of features:", features.shape[1])
print("Number of classes:", len(np.unique(labels)))

# Split the dataset into training, testing, and evaluation sets
X_train, X_temp, y_train, y_temp = train_test_split(features, labels, test_size=0.3, random_state=42)
X_test, X_eval, y_test, y_eval = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42)

# Standardize the features using StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
X_eval = scaler.transform(X_eval)

# Define the parameter grid for KNN and perform grid search for hyperparameter tuning
param_grid = {'n_neighbors': [3, 5, 7], 'weights': ['uniform', 'distance']}
knn = KNeighborsClassifier()
grid_search = GridSearchCV(knn, param_grid, cv=5, scoring='accuracy')
grid_search.fit(X_train, y_train)

# Display the best hyperparameters found by grid search
print("Best Hyperparameters:", grid_search.best_params_)

# Train the KNN model with the best hyperparameters on the training set
best_knn_model = grid_search.best_estimator_
best_knn_model.fit(X_train, y_train)

# Evaluate the model on the test set and display the accuracy
y_pred = best_knn_model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("Test Accuracy:", accuracy)

# Evaluate the model on the evaluation set and display the accuracy
y_eval_pred = best_knn_model.predict(X_eval)
eval_accuracy = accuracy_score(y_eval, y_eval_pred)
print("Evaluation Accuracy:", eval_accuracy)

# Display the classification report for the evaluation set
print("\nClassification Report:")
print(classification_report(y_eval, y_eval_pred))

# Confusion Matrix
cm = confusion_matrix(y_eval, y_eval_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False)
plt.title('Confusion Matrix')
plt.xlabel('Predicted Labels')
plt.ylabel('True Labels')
plt.show()

# Bar plot for Precision, Recall, and F1-score
report_dict = classification_report(y_eval, y_eval_pred, output_dict=True)
metrics = ['precision', 'recall', 'f1-score']
classes = [str(i) for i in range(10)]  # Assuming you have 10 classes

for metric in metrics:
    values = [report_dict[class_label][metric] for class_label in classes]
    plt.figure(figsize=(10, 5))
    sns.barplot(x=classes, y=values, palette='viridis')
    plt.title(f'{metric.capitalize()} for Each Class')
    plt.xlabel('Class')
    plt.ylabel(metric.capitalize())
    plt.show()
