# -*- coding: utf-8 -*-
# Created on Sat Sep 31 08:15:00 2024
# @author: mhebding
# DM3 - Intelligence artificielle 2



## 1 - Prediction KNN

import matplotlib.pyplot as plt
from math import sqrt

donneesApprentissage = [
[2,3,'chat'], [1,5,'lapin'], [4,5,'chat'], [5,7,'chat'], [2,10,'lapin'],
[1.2,8,'lapin'], [7,5,'chat'], [3.5,4.5,'chat'], [3,12,'lapin'], [4.2,13,'lapin'], 
[8,6,'lapin'], [2,4,'chat'], [3,5,'chat'], [3.5, 8,'lapin'], [6,7,'lapin'], 
[3,6,'chat'], [3.8,7,'chat'], [8,15,'lapin'], [7,10,'lapin'], [4.5,8,'chat'],
[10,6,'chat'], [8,9,'chat'], [1,6,'lapin'], [2.5,9,'lapin'], [7,7,'chat'],
[2,8,'chat'], [3,7,'chat'], [8,7,'lapin'], [5.5,9.5,'lapin'], [5,3,'chat'],
[9,6,'chat'], [7,8,'lapin'], [4,7,'lapin'], [3,10,'lapin'], [9,7,'chat'],
[5.5,7.5,'lapin'], [5,9,'chat'], [9,9,'lapin'], [0.9,7,'lapin'], [6,9,'chat']
] 

donneesTest = [
[4,8,'chat'], [2.1,2,'chat'], [4.2,6,'lapin'], [3,6,'chat'], [4.9,8,'lapin'], 
[9,12,'lapin'], [2.5,3,'chat'], [8,4,'chat'], [12,2,'chat'], [6,5,'lapin']
]

donneesInconnues = [
[8.5,6.5], [1.5,4.5], [5.5,6.5], [9.5,6.5], [9,8]
]

# 1.
def distanceEuclidienne(donnee1, donnee2) : 
    distance = sqrt((donnee1[0]-donnee2[0])**2+(donnee1[1]-donnee2[1])**2)
    return distance

# 2.
def kPlusProches(table, nouveau, k) : # peut s'ameliorer avec =sorted(table, key=distanceEuclidienne)  
    n = len(table)
    distances = []
    for i in range(n):
        distance_element = distanceEuclidienne(table[i],nouveau)
        distances.append(distance_element)
    table_triee = [i for _,i in sorted(zip(distances,table))] # astuce
    plus_proches_voisins = []
    for i in range(k) : 
        plus_proches_voisins.append(table_triee[i])
    return plus_proches_voisins

# 3.
def rayon(voisins, nouveau):
    distances = []
    for element in voisins:
        distance = distanceEuclidienne(element, nouveau)
        distances.append(distance)
    distance_max = max(distances)
    return distance_max

# 4.
def population(table, classe):
    pop = 0
    for element in table:
        if element[2] == classe:
            pop += 1
    return pop

# 5.
def maxVoisins(table):
    popChats = population(table, 'chat')
    popLapins = population(table, 'lapin')
    if popChats > popLapins:
        resultat = 'chat'
    elif popLapins > popChats:
        resultat = 'lapin'
    else:
        resultat = 'mi-chat-mi-lapin'
    return resultat

# 6.
def essai(table, nouveau, k):
    voisins = kPlusProches(table, nouveau, k)
    classeNouveau = maxVoisins(voisins)
    print("Sur", k, "voisins, il y a", population(voisins,'chat'), "chats et", population(voisins, 'lapin'), "lapins : la cible pourrait être un", classeNouveau)

print('Essai')
print(essai(donneesApprentissage, donneesTest[5], k=3))

# 7.
def distanceManhattan(donnee1, donnee2) : 
    distance = sqrt((donnee1[0]-donnee2[0])**2+(donnee1[1]-donnee2[1])**2)
    return distance

def kPlusProches2(table, nouveau, k) : # peut s'améliorer avec =sorted(table, key=distanceEuclidienne)  
    n = len(table)
    distances = []
    for i in range(n):
        distance_element = distanceManhattan(table[i],nouveau)
        distances.append(distance_element)
    table_triee = [i for _,i in sorted(zip(distances,table))] # astuce
    plus_proches_voisins = []
    for i in range(k) : 
        plus_proches_voisins.append(table_triee[i])
    return plus_proches_voisins

def essai2(table, nouveau, k):
    voisins = kPlusProches2(table, nouveau, k)
    classeNouveau = maxVoisins(voisins)
    print("Sur", k, "voisins, il y a", population(voisins,'chat'), "chats et", population(voisins, 'lapin'), "lapins : la cible pourrait être un", classeNouveau)

print('Essai2')
print(essai2(donneesApprentissage, donneesTest[5], k=3))



## 2 - Influence de k et matrice de confusion

# 8.
def maxOK():
    resultats = []
    for k in range(21):
        OK = 0
        KO = 0
        temp = []
        for element in donneesTest:
            nouveau = element
            voisins = kPlusProches(donneesApprentissage, nouveau, k)
            classeNouveau = maxVoisins(voisins)
            if classeNouveau == element[2]:
                OK += 1
            else:
                KO += 1
        temp.append(OK)
        temp.append(KO)
        resultats.append(temp)
    print('Meilleur k: '+str(resultats.index(max(resultats)))+' pour '+str(max(resultats)[0])+'/'+str(len(donneesTest)) + ' OK')

print(maxOK())

# 9.
#for element in donneesInconnues:    
#    essai(donneesApprentissage, element, 7)
# ce sont tous des chats

# 10.
# vrai chat = chat predit chat 
# vrai lapin = lapin predit lapin
# faux chat = lapin predit chat
# faux lapin = chat predit lapin

# 11.
# TP = vrai chat
# TN = vrai lapin
# FP = faux chat
# FN = faux lapin

# 12.
def matriceConfusion():
    k=7
    TP = 0 #vrai chat (00)
    TN = 0 #vrai lapin (11)
    FP = 0 #predit chat mais lapin (01)
    FN = 0 #predit lapin mais chat (10)
    for element in donneesTest:
        nouveau = element
        voisins = kPlusProches(donneesApprentissage, nouveau, k)
        classeNouveau = maxVoisins(voisins)
        if classeNouveau == element[2] and element[2]=='chat':
            TP += 1
        elif classeNouveau == element[2] and element[2]=='lapin':
            TN += 1
        elif classeNouveau != element[2] and element[2]=='chat':
            FN += 1
        elif classeNouveau != element[2] and element[2]=='lapin':
            FP += 1
    matrice = [[],[]]
    matrice[0].append(TP)
    matrice[0].append(FP)
    matrice[1].append(FN)
    matrice[1].append(TN)
    print(TP,FP)
    print(FN,TN)
    return matrice

# 13.
print('Matrice de confusion')
print(matriceConfusion())

# 14.
Matrice = matriceConfusion()
rappel = Matrice[0][0]/(Matrice[0][0]+Matrice[1][0])
print('Metrique rappel')
print(rappel)

# 15.
precision = Matrice[0][0]/(Matrice[0][0]+Matrice[0][1])
print('Metrique precision')
print(precision)

# 16.
specificite = Matrice[1][1]/(Matrice[0][1]+Matrice[1][1])
print('Metrique specificite')
print(specificite)

# 17.
F = 2*Matrice[0][0]/(2*Matrice[0][0]+Matrice[0][1]+Matrice[1][0])
print('Metrique F')
print(F)

# 18.
# commentaires

def grapheMatrice():
    matrice = matriceConfusion()
    labels = ['chat', 'lapin']
    fig, ax = plt.subplots()
    ax.imshow(matrice,interpolation='none',cmap='Blues')
    for i in range(len(matrice[0])):
        for j in range(len(matrice[1])):
            ax.text(j, i,s=matrice[i][j], va='center', ha='center')
    ax.set_xlabel("Verite")
    ax.set_ylabel("Prediction")
    ax.set_yticks([0, 1], minor = False)
    ax.yaxis.set_ticklabels(labels, minor = False)
    ax.set_xticks([0, 1], minor = False)
    ax.xaxis.set_ticklabels(labels, minor = False)
    plt.show()

grapheMatrice()

def graphe(table, tableTest, nouveau):
    def listesAttributs(table, classe):
        liste_x, liste_y = [], []
        for element in table:
            if element[2] == classe:
                liste_x.append(element[0])
                liste_y.append(element[1])
        return liste_x, liste_y

    liste_x_1, liste_y_1 = listesAttributs(table, 'chat')
    liste_x_2, liste_y_2 = listesAttributs(table, 'lapin')
    liste_x_3 = [element[0] for element in tableTest]
    liste_y_3 = [element[1] for element in tableTest]

    fig, ax = plt.subplots()
    plt.axis('equal')
    plt.scatter(liste_x_1,liste_y_1, label='chats')
    plt.scatter(liste_x_2,liste_y_2, label='lapins')
    plt.scatter(liste_x_3, liste_y_3, label='test', color='k')
    plt.scatter(nouveau[0], nouveau[1], label='nouveau', color='r')
    plt.xlabel('poids (kg)')
    plt.ylabel('oreilles (cm)')
    voisins = kPlusProches(table, nouveau, k)
    cercle = plt.Circle((nouveau[0], nouveau[1]), rayon(voisins, nouveau), fill=False, ls='dotted') # 10.
    ax.add_patch(cercle)
    plt.legend()
    plt.show()

k=7
graphe(donneesApprentissage, donneesTest, donneesTest[0])