######################################################################################
#                                 Régression logistique                              #
######################################################################################

## Importation des bibliothèques

import numpy as np                          # Numpy
import matplotlib.pyplot as plt             # Matplotlib : Affichage graphiques
import random as rd
from matplotlib.colors import ListedColormap# Fonction pour choix d'une couleur
from matplotlib.markers import MarkerStyle  # Fonction pour choix d'un marqueur
from mpl_toolkits.mplot3d import Axes3D     # Axes3D : Graphique en dimension 3
                                            #           z = f(xa,xb)


## Importation des données

def Import(fichier):
    f1=open(fichier,'r')
    ligne=f1.readline()
    ligne=f1.readline()
    Tab=[]
    while ligne!='':
        ligne=ligne.strip('\n')
        Liste=ligne.split('\t')
        Tab.append([float(Liste[0])/1000,float(Liste[1]),float(Liste[2])])
        ligne=f1.readline()
    f1.close()
    Donnees=np.array(Tab)
    Entrees,Sorties=Donnees[:,:2],Donnees[:,2:3]
    return Entrees,Sorties

def Sp_donnees(Entrees,Sorties):
    EA,ET=[],[]
    SA,ST=[],[]
    for i in range(len(Entrees)):
        if (i+1)%4==0:
            ET.append(Entrees[i])
            ST.append(Sorties[i])
        else:
            EA.append(Entrees[i])
            SA.append(Sorties[i])
    ET=np.array(ET)
    EA=np.array(EA)
    ST=np.array(ST)
    SA=np.array(SA)
    return EA,ET,SA,ST

Entrees,Sorties=Import("Achat.txt")   # Importation des données du fichier ".txt"
        # Séparation des données en 2 jeux de données (Apprentissage et Test)
Entr_Appr,Entr_Test,Sort_Appr,Sort_Test=Sp_donnees(Entrees,Sorties)

def affichage_donnees_3D(entrees,sorties):
    ax=plt.axes(projection='3d')
    ax.scatter(entrees[:,0],entrees[:,1],sorties[:,0],c=sorties[:,0],cmap=ListedColormap(['blueviolet','red']))
    ax.set_xlabel('Salaire')
    ax.set_ylabel('Age')
    ax.set_zlabel('Achat')
    plt.show()


## Mise en forme des données

def Mat_donnees(entrees_brutes:np.array,L_degres:list)->np.array:
    Ndonnees,Nparam=np.shape(entrees_brutes)
    donnees=np.ones((Ndonnees,1+sum(L_degres)))
    colonne=1
    for ip in range(Nparam):
        degre=1
        for id in range(L_degres[ip]):
            donnees[:,colonne]=(entrees_brutes[:,ip])**degre
            degre+=1
            colonne+=1
    return donnees

def normalisation_donnes(donnees):
    mu = np.ones(donnees.shape[1])          #  Moyennes des entrées
    sigma = np.zeros(donnees.shape[1])      # Ecart type des entrées
    donnees_normal=np.ones(donnees.shape)   # Matrice des entrées normalisées
    for k in range(1,donnees.shape[1]):     # Pour chaque colonne sauf celle des "1"
        mu[k] = np.mean(donnees[:,k])                   # Calcul de la moyenne
        sigma[k] = np.std(donnees[:,k])                 # Calcul de l'écart type
        donnees_normal[:,k] = (donnees[:,k]-mu[k])/sigma[k]  # Normalisation des données
    return donnees_normal,mu,sigma

def Init_vecteur_parametres(X):
    Nbr_para=np.shape(X)[1]
    Winit=[[0.5]]
    Winit=Winit+[[rd.random()] for i in range(1,Nbr_para)]
    return np.array(Winit)

## Algorithme de la régression

def Regression(Entrees,Sorties,L_degre,Nbr_iter,alpha,Epsilon):
        # Construction de la matrice de données
    Xd=Mat_donnees(Entrees,L_degre)
        # Normalisation des données et initialisation du vecteur paramètres W
    Xn,mu,sigma=normalisation_donnes(Xd)
    Wn=Init_vecteur_parametres(Xn)
    print('Parametres initiaux de la régression :')
    print(Wn)
        # Initialisation du Delta_coût et du compteur
    cout=Cout(Xn,Wn,Sorties)
    HC=[cout]                   # Historique des coûts
    CP=np.inf                   # Coût précédent (avant la 1iere itération il est infini)
    k=0                         # Compteur d'itérations
    Delta_Cout=abs(CP-HC[-1])   # Variation du coût
    print(cout,'\t',k,'\t','\t',Delta_Cout)
        # Boucle d’apprentissage par la descente de gradiant
    while (k<Nbr_iter and Delta_Cout>Epsilon):
        Wn=Wn-alpha*gradient(Xn,Wn,Sorties)   # Actualisation du vecteur paramètres
            # Calcul du nouveau Delta_coût
        CP=HC[-1]
        cout=Cout(Xn,Wn,Sorties)
        HC.append(cout)
        Delta_Cout=abs(CP-HC[-1])
            # Affichage du Coût, du compteur et du Delta_coüt
            #  toutes les 1000 itérations
        k+=1    # Incrémentation du compteur d'itération
        if k%1000==0:
            print("A la",k,"ième itération le coût est de :",cout)
                # Le vecteur paramètres est celui des données normalisées
                # Caclul des paramètres des données avant normalisation
    W=np.zeros((len(Wn),1))
    W[0,0]=Wn[0,0]
    for i in range(1,len(Wn)):
        W[0,0]-=Wn[i,0]*mu[i]/sigma[i]
        W[i,0]=Wn[i,0]/sigma[i]
            # Affiche graphique des données et de la régression
    print("Nombre d'itérations effectuées pour l'apprentissage :",k)
    if k == Nbr_iter : print("Dernière variation du coût :",Delta_Cout)
    Ecriture_regression(W,k,L_degre,HC)
    trace_regression_3D(Entr_Test,Sort_Test,W,L_degre)
    print('Voici la matrice de confusion : Achat : 0 = Oui 1 = Non')
    print('Les lignes sont les labels des données Test, les colonnes les prédictions')
    # print(Matrice_de_confusion(Entr_Test,Sort_Test,L_degre,W))
    # return W,HC,k


# def sig(X:np.array,W:np.array)-> np.array:          # Fonction sigmoïde



# def Cout(X:np.array,W:np.array,Y:np.array)->float:  # Fonction de calcul du coût







# def gradient(X:np.array,W:np.array,Y:np.array)->float: # Calcul du gradient





## Affichage des résultats et des données dans un graphique 3D

def Ecriture_regression(W,k,L_degre,Liste_Couts):
    print("La régression a été construite en",k,"itérations")
    print("Le modèle de prédiction est :")
    equation='y = sigmoïde [ w0 + w1.xa'
    for j in range(2,L_degre[0]+1):
        equation +=' + w'+str(j)+'.xa^'+str(j)
    equation+=' + w'+str(L_degre[0]+1)+'.xb'
    for j in range(2,L_degre[1]+1):
        equation +=' + w'+str(L_degre[0]+j)+'.xb^'+str(j)
    equation +=' ]'
    print(equation)
    print('Les paramètres wi de cette équation sont (de w0 à w'+str(sum(L_degre))+') :')
    print(W)

def trace_regression_3D(entrees,sorties,W,L_degre):
    def polynome(x,y):
        r=W[0,0]
        for k in range(1,L_degre[0]+1):
            r+=W[k,0]*x**k
        for k in range(1,L_degre[1]+1):
            r+=W[L_degre[0]+k,0]*y**k
        return r
    f = lambda sx, sy : 1/(1+np.exp(-1*polynome(sx,sy)))
    GX = np.linspace(10,150,29)
    GY = np.linspace(10,70,31)
    GX, GY = np.meshgrid(GX, GY)
    Z = f(GX, GY)
    ax=plt.axes(projection='3d')
    ax.scatter(entrees[:,0],entrees[:,1],sorties[:,0],c=sorties[:,0],cmap=ListedColormap(['blueviolet','red']))
    ax.plot_surface(GX, GY, Z, cmap='rainbow', alpha=0.8)
    ax.set_xlabel('Salaire')
    ax.set_ylabel('Age')
    ax.set_zlabel('Achat')
    plt.show()

## Evaluation de l'algorithme sur les données test

def Matrice_de_confusion(entrees_t,sortie_t,L_degre,W):
    def polynome(xs,xa):                    # fonction polynomiale f(xs,xa)
        r=W[0,0]
        for k in range(1,L_degre[0]+1):
            r+=W[k,0]*xs**k
        for k in range(1,L_degre[1]+1):
            r+=W[L_degre[0]+k,0]*xa**k
        return r
    MatConf=np.zeros((2,2))
    for i in range(len(entrees_t)):
        # label=
        # proba=
        if proba>=0.5 : predi=int(1)
        else : predi=int(0)
        MatConf[label,predi]+=1
    return MatConf






