import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.metrics import accuracy_score, classification_report
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import seaborn as sns

# Load the Fashion MNIST dataset from the file
mnist_df = pd.read_csv('fashion-mnist_test.csv')

# Normalize and scale the data
scaler = StandardScaler()
X = mnist_df.iloc[:, 1:]
X = scaler.fit_transform(X)

# Split the dataset into train, test, and evaluation subsets
X_train, X_temp, y_train, y_temp = train_test_split(X, mnist_df['label'], test_size=0.4, 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)

# Define the decision tree model
dt_classifier = DecisionTreeClassifier()

# Perform hyperparameter tuning using GridSearchCV
param_grid = {
    'criterion': ['gini', 'entropy'],
    'max_depth': [None, 10, 20, 30, 40],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

# Use GridSearchCV to find the best hyperparameters
grid_search = GridSearchCV(dt_classifier, param_grid, cv=3)
grid_search.fit(X_train, y_train)

# Get the best hyperparameters
best_params = grid_search.best_params_
print("Best Hyperparameters:", best_params)

# Train the model with the best hyperparameters
best_dt_classifier = DecisionTreeClassifier(**best_params)
best_dt_classifier.fit(X_train, y_train)

# Visualize the decision tree
plt.figure(figsize=(15, 10))
# Convert the Index object to a list for 'feature_names'
feature_names_list = X.columns.tolist()
plot_tree(best_dt_classifier, filled=True, feature_names=feature_names_list, class_names=[str(i) for i in range(10)])
plt.title('Decision Tree Visualization')
plt.show()

# Evaluate the model on the test set
y_pred = best_dt_classifier.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("Test Set Accuracy:", accuracy)

# Display the classification report for the test set
print("Classification Report for Test Set:\n", classification_report(y_test, y_pred))

# Evaluate the model on the evaluation set
y_eval_pred = best_dt_classifier.predict(X_eval)
eval_accuracy = accuracy_score(y_eval, y_eval_pred)
print("Evaluation Set Accuracy:", eval_accuracy)

# Display the classification report for the evaluation set
print("Classification Report for Evaluation Set:\n", classification_report(y_eval, y_eval_pred))

# Add a bar chart to compare accuracy between test and evaluation sets
accuracy_comparison = pd.DataFrame({'Dataset': ['Test Set', 'Evaluation Set'],
                                     'Accuracy': [accuracy, eval_accuracy]})
plt.figure(figsize=(8, 5))
sns.barplot(x='Dataset', y='Accuracy', data=accuracy_comparison, palette='viridis')
plt.title('Model Accuracy Comparison')
plt.ylim(0, 1)
plt.show()

# Add a bar chart to visualize class-wise precision, recall, and f1-score for the evaluation set
eval_classification_rep = classification_report(y_eval, y_eval_pred, output_dict=True)
class_report_df = pd.DataFrame(eval_classification_rep).transpose()
class_report_df = class_report_df.reset_index()
class_report_df = class_report_df.rename(columns={'index': 'Class'})

plt.figure(figsize=(12, 8))
sns.barplot(x='Class', y='precision', data=class_report_df, palette='Blues', label='Precision')
sns.barplot(x='Class', y='recall', data=class_report_df, palette='Oranges', label='Recall')
sns.barplot(x='Class', y='f1-score', data=class_report_df, palette='Greens', label='F1-Score')
plt.title('Class-wise Metrics for Evaluation Set')
plt.legend()
plt.show()
