diff --git a/projet_knn.py b/projet_knn.py new file mode 100644 index 0000000..f02b347 --- /dev/null +++ b/projet_knn.py @@ -0,0 +1,181 @@ +import tkinter as tk +from math import sqrt + +def transfoEnFloat(groupe): + """Prend en parametre un tuple ou une liste de réels de type str +et renvoie une liste de réels de type float""" + lst = [] + for element in groupe: + lst.append(float(element)) + return lst + +def lecture(nomFichier): + """Prend en parametre le nom du fichier csv à lire et renvoie un tableau.""" + with open(nomFichier, 'r') as fichier: + fichier_nettoye = [] + tableau = [] + for ligne in fichier: + fichier_nettoye.append(ligne.strip()) + for i in range(1,len(fichier_nettoye)): + tableau.append(tuple(fichier_nettoye[i].split(","))) + return tableau + +def estBissextile(annee): + """prend en compte une annee à verifier si elle est bissextile et renvoie +True si elle l'est et False sinon""" + return annee%4 == 0 and annee%100 != 0 or annee%400 == 0 + +def numeroJour(date): + """Prend en parametre une date au format aaaa-mm-jj et renvoie un entier +correspondant au numero du jour correspondant (de 1 à 365)""" + dateSeparee = transfoEnFloat(date.split('-')) + totalJours = 0 + JoursMois = [31,28,30,31,30,31,31,30,31,30,31] #Nombre de jours de chaques mois + for i in range(int(dateSeparee[1])-1): + totalJours += JoursMois[i] + if estBissextile(dateSeparee[0]) and totalJours + dateSeparee[2] >= 60 and dateSeparee[1] > 2: + #regarde si on est dans une année bissextile et apres fevrier + totalJours += 1 + return totalJours + dateSeparee[2] + +def distanceEuclidienne(Tuple1 , Tuple2): + """prend en parametre 2 tuples avec (Numéro du jour, Valeur1, Valeur2) +et renvoie la distance **euclidienne**""" + lst1 = transfoEnFloat(Tuple1) + lst2 = transfoEnFloat(Tuple2) + minJours = min([lst1[0],lst2[0]]) + maxJours = max([lst1[0],lst2[0]]) + DiffJours = min([maxJours-minJours,365-maxJours+minJours]) + return sqrt(DiffJours**2 + (lst1[1]-lst2[1])**2+(lst1[2]-lst2[2])**2) + +def distanceManhattan(Tuple1, Tuple2): + """prend en parametre 2 tuples avec (numero du jour, Valeur1, Valeur2) +et renvoie la distance de manhattan""" + lst1 = transfoEnFloat(Tuple1) + lst2 = transfoEnFloat(Tuple2) + minJours = min([lst1[0],lst2[0]]) + maxJours = max([lst1[0],lst2[0]]) + DiffJours = min([maxJours-minJours,365-maxJours+minJours]) + return DiffJours + abs(lst1[1]-lst2[1]) + abs(lst1[2]-lst2[2]) + +def kPlusProches(point,lstRepre,k): #je ne sais pas si il faut mettre k ce n'est pas demandé dans l'ennoncé mais cela ne servirait à rien d'en mettre plus + """Prend en parametre un tuple avec (Numéro du jour, Température moyenne, + température de référence) correspondant au point à verifier, une liste de + tuples de meme type et la consommation electrique + Renvoie une liste triée en fonction de la distance avec le tuple dont les + elements sont des tuple avec (distance, indice du tableau original)""" + lstvoisins = [] + if k > len(lstRepre): + k = len(lstRepre) + for i in range(len(lstRepre)): + if boolDistanceDeManhattan: + d = distanceManhattan(point,lstRepre[i]) + else: + d = distanceEuclidienne(point,lstRepre[i]) + lstvoisins.append((d,i)) + lstvoisins.sort() + return [lstvoisins[i] for i in range(k)] + +def puissanceMoyenne(listeTriee, tableau): + """Prend une liste triée de tuple avec (distance, indice du tableau initial) + et un tableau de donnée + et renvoie la moyenne de la puissance electrique des k plus proches voisins""" + PuissanceTotale = 0.0 + for donnee in listeTriee: + distance,indice = donnee + PuissanceTotale += float(tableau[indice+1][1]) + return PuissanceTotale/len(listeTriee) +def listeTuples(fichier): + """Prend une liste de tuples de type (date,pic journalier consommation, +temperature moyenne, temperature reference) et renvoie une liste de tuples de type +(numero jour, temperature moyenne, temperature de reference)""" + tableau = [] + for i in range(1,len(fichier)): + date, picJournalier, tempMoy, tempRef = fichier[i] + tableau.append((numeroJour(date),tempMoy,tempRef)) + return tableau +def lancerTest(): + """Utilise les valeurs données par l'utilisateur et le fichier pic journaliser pour afficher la moyenne de consommation electrique""" + nombreK = int(valeurk.get()) + + date = numeroJour(valeurdate.get()) + temp = valeurtemp.get().split(" ") + temp = transfoEnFloat(temp) + tempMoy, tempRef = temp + + fichier = lecture('pic-journalier-consommation-brute-2023.csv') + + listeVoisins = kPlusProches((date,tempMoy,tempRef),(listeTuples(fichier)), + nombreK) + ConsoMoy = round(puissanceMoyenne(listeVoisins,fichier),2) + affichageResultats.configure(text="La consommation électrique devrait être d'environ " + str(ConsoMoy) +"MW") + +def changeDistance(): + global boolDistanceDeManhattan + if boolDistanceDeManhattan: + boutonDistance.config(text='Distance euclidienne') + else: + boutonDistance.config(text='Distance de Manhattan') + boolDistanceDeManhattan = not boolDistanceDeManhattan + +boolDistanceDeManhattan = False + +fenetre = tk.Tk() +fenetre['bg'] = "snow" +fenetre.title("Outil de prévision énergétique") +fenetre.resizable(False,False) +#Un truc comme gestionnaire du transport d'electricité non ? ou je pense que sur tkinter tu peux faire disparaitre + #le dessus (mais faut rajouter un bouton pour fermer la fenetre et tout c'est galere) + +canva = tk.Canvas(fenetre, bg = "snow", highlightthickness = 0, width = 10, + height = 13.5) +canva.grid( columnspan = 2) +# Boutons valeur de k +valeurk = tk.StringVar() +labelk = tk.Label(fenetre, text = "choisir le nombre de k plus proches voisin voulus : ", + width = 50, bg = "snow") +labelk.grid(row = 1, column = 0, columnspan = 2) +entreek = tk.Entry(fenetre, textvariable = valeurk, width = 30) +entreek.grid(row = 2, column = 0, columnspan = 2) + +# Boutons date +canvadate = tk.Canvas(fenetre, bg = "snow", width = 350, height = 20, + highlightthickness = 0) +canvadate.create_line(0, 10, 350, 10, fill = "black") +canvadate.grid(row = 3, columnspan = 2) +valeurdate = tk.StringVar() +labeldate = tk.Label(fenetre, text = "choisir la date (au format aaaa-mm-jj) : ", width = 50, bg = "snow") +labeldate.grid(row = 4, column = 0, columnspan = 2) +entreedate=tk.Entry(fenetre, textvariable = valeurdate, width = 30) +entreedate.grid(row = 5, column = 0, columnspan = 2) + +# Boutons température +canvatemp = tk.Canvas(fenetre, bg = "snow", width = 350, height = 20, + highlightthickness = 0) +canvatemp.create_line(0, 10, 350, 10, fill = "black") +canvatemp.grid(row = 6, columnspan = 2) +valeurtemp = tk.StringVar() +labeltemp = tk.Label(fenetre, text = "La température moyenne et de référence (avec un espace) : ", + width = 50, bg = "snow") +labeltemp.grid(row = 7, column = 0, columnspan = 2) +entreetemp=tk.Entry(fenetre, textvariable = valeurtemp, width = 30) +entreetemp.grid(row = 8, column = 0, columnspan = 2) + +canvaRecup = tk.Canvas(fenetre, bg='snow', width = 1, height = 10, highlightthickness = 0) +canvaRecup.grid(row = 9, columnspan = 2) + +labeldistance = tk.Label(fenetre, text = "mode de distance actuel :", bg = "snow") +labeldistance.grid(row = 10) + +boutonRecup = tk.Button(fenetre, command = lancerTest, bg = 'snow',text='Valider', + cursor='hand2') +boutonRecup.grid(row = 11,column = 1) + +boutonDistance = tk.Button(fenetre, command = changeDistance, text="Distance euclidienne" + ,bg='snow',cursor='hand2') +boutonDistance.grid(row=11, column=0) + +affichageResultats = tk.Label(fenetre, bg='snow', text='',font='TkDefaultFont 8 bold') #J'ai du chercher parce qu'on ne peut +affichageResultats.grid(row=12, columnspan = 2) + +fenetre.mainloop() \ No newline at end of file