#!/usr/bin/python
# -*- coding: utf-8 -*-
# Implementação do k-NN para "iris data set"
"""
Created on Fri Jan 15 21:05:07 2021

@author: Vinicius
https://www.monolitonimbus.com.br/classificacao-usando-knn/
"""


import sys
from scipy.spatial import distance
import math
import random
import numpy as np
import statistics
######################### FUNÇÕES #########################

# Imprime informações sobre os dados de iris.data
def info_dataset(amostras, verbose=True):
	if verbose:
		print('Total de amostras: %d' % len(amostras))
	rotulo1, rotulo2, rotulo3 = 0, 0, 0
	for amostra in amostras:
		if amostra[-1] == 'Iris-setosa':
			rotulo1 += 1
		elif amostra[-1] == 'Iris-versicolor':
			rotulo2 += 1
		elif amostra[-1] == 'Iris-virginica':
			rotulo3 += 1
		else:
			print("nenhuma classificação!")
	if verbose:
		print('Total rotulo 1: %d' % rotulo1)
		print('Total rotulo 2: %d' % rotulo2)
		print('Total rotulo 3: %d' % rotulo3)
	return [len(amostras), rotulo1, rotulo2, rotulo3]


# KNN propriamente dito
def knn(treinamento, instanciaTeste, K): 
	tam_treino = len(treinamento)
	# calcula as distâncias euclidianas da instanciaTeste a todas amostras de treinamento
	dist=[distance.euclidean(treinamento[i][:-1], instanciaTeste[:-1]) for i in range(tam_treino)]
	k_vizinhos=np.argsort(dist)[:K] #índice das k menores distâncias
	# votação majoritária
	lst = [treinamento[l][-1] for l in k_vizinhos]

	return statistics.mode(lst) #retorna a moda, ou seja, o índice K com maior contagem


######################### PROGRAMA #########################

# Inicialização da lista de amostras
amostras = []

# Carregar dados de arquivo com amostras
with open('iris.data', 'r') as f:
	#ler arquivo linha por linha
	for linha in f.readlines():
		# obter os atributos da amostra
		atrib = linha.replace('\n','').split(',') #separa os dados conforme a vírgula
		amostras.append([float(atrib[0]), float(atrib[1]), float(atrib[2]), float(atrib[3]), atrib[4]])
	random.shuffle(amostras) # mistura a ordem dos dados
    
# Guardar totais de cada rótulo/classe
_, rotulo1, rotulo2, rotulo3 = info_dataset(amostras, verbose=False)

# Separar 60% dos dados para treinamento e o restante fica para testar
p = 0.6
treinamento, teste = [], []
cont = 1
#atualiza quantidade de 60% da amostra para treinamento
max_rotulo1, max_rotulo2, max_rotulo3 = int(p*rotulo1), int(p*rotulo2), int(p*rotulo3)
total_rotulo1, total_rotulo2 , total_rotulo3 = 0, 0, 0

for amostra in amostras:
    if amostra[-1] == 'Iris-setosa' and total_rotulo1 < max_rotulo1:
        total_rotulo1 += 1
        treinamento.append(amostra)
    elif amostra[-1] == 'Iris-versicolor' and total_rotulo2 < max_rotulo2:
        total_rotulo2 += 1
        treinamento.append(amostra)
    elif amostra[-1] == 'Iris-virginica' and total_rotulo3 < max_rotulo3:
        total_rotulo3 += 1
        treinamento.append(amostra)
    else:
    	teste.append(amostra)
    	cont += 1

# Testar com as amostras do conjunto de teste
acertos = 0
K = 13
matConf=np.zeros((4,4))

for amostraTeste in teste:
    	classe = knn(treinamento, amostraTeste, K)
    	#print('%s X %s' % (amostra[-1], classe))
    	#sys.exit("fim de teste")
    
        #knn acertou
    	if amostraTeste[-1] == classe: 
    		acertos += 1
    		if classe == 'Iris-setosa': #0
    			matConf[0][0]+=1        #era setosa e o resultou setosa
    			matConf[3][0]+=1        #atualiza total de acertos para setosa
    			matConf[0][3]+=1        #soma total de (setosa,versicolor, virginica)
    		elif classe == 'Iris-versicolor': #1
    			matConf[1][1]+=1
    			matConf[3][1]+=1
    			matConf[1][3]+=1  #total de versicolor
    		elif classe == 'Iris-virginica': #2
    			matConf[2][2]+=1
    			matConf[3][2]+=1
    			matConf[2][3]+=1  #total de virginica
            
        #knn errou, era setosa        
    	elif amostraTeste[-1] == 'Iris-setosa': #0
            matConf[0][3]+=1    #total de setosa 
            if classe == 'Iris-versicolor': #1      
                matConf[0][1]+=1
                matConf[3][1]+=1
    			
            elif classe == 'Iris-virginica': #2
                matConf[0][2]+=1
                matConf[3][2]+=1
    			
            
        #knn errou, era versicolor        
    	elif amostraTeste[-1] == 'Iris-versicolor': #1
            matConf[1][3]+=1     #total de versicolor 
            if classe == 'Iris-setosa': #0
                matConf[1][0]+=1
                matConf[3][0]+=1
                
            elif classe == 'Iris-virginica': #2
                matConf[1][2]+=1
                matConf[3][2]+=1
                
            
        #knn errou, era virginica        
    	elif amostraTeste[-1] == 'Iris-virginica': #2
            matConf[2][3]+=1  #total de virginica
            if classe == 'Iris-setosa': #0
                matConf[2][0]+=1
                matConf[3][0]+=1
                
            elif classe == 'Iris-versicolor': #1
                matConf[2][1]+=1
                matConf[3][1]+=1
               
                
    	matConf[3][3]=np.sum(matConf[3][:-1])    

print('Total de treinamento: %d' % len(treinamento))
print('Total de testes: %d' % len(teste))
print('Total de acertos: %d' % acertos)
print('Porcentagem de acertos: %.2f%%' % (100 * acertos / len(teste)))
print('Matriz de confusão:')

print("    | set | ver  | vir  | tot |")
print('set | %2d  | %2d   | %2d   | %2d  |' %(matConf[0][0],matConf[0][1],matConf[0][2],matConf[0][3]))
print('ver | %2d  | %2d   | %2d   | %2d  |' %(matConf[1][0],matConf[1][1],matConf[1][2],matConf[1][3]))
print('vir | %2d  | %2d   | %2d   | %2d  |' %(matConf[2][0],matConf[2][1],matConf[2][2],matConf[2][3]))
print('tot | %2d  | %2d   | %2d   | %2d  |' %(matConf[3][0],matConf[3][1],matConf[3][2],matConf[3][3]))

prin('')
