APRENDIZAGEM SUPERVISIONADA · ÁRVORE DE DECISÃO

Transforme uma tabela mista em perguntas “se/então”.

A árvore procura divisões que deixam as classes mais puras. Nesta página, números contínuos, contagens discretas e categorias em texto são tratados diretamente por um algoritmo implementado do zero.

visitas ≤ 2,5?
simnão
plano = Premium?
Compra: Sim
Cada caminho termina em uma previsão.

01 · DADOS TABULARES MISTOS

A coluna determina o tipo de pergunta.

Não é necessário transformar toda categoria textual em um número arbitrário. O algoritmo identifica o tipo da característica e gera candidatos de divisão adequados.

CONTÍNUA

idade

Pode assumir valores numa escala. A árvore ordena os valores e testa limiares entre vizinhos.

idade ≤ 38,5?
DISCRETA

visitas

É uma contagem inteira, mas continua sendo ordenável. Os candidatos também são limiares.

visitas ≤ 2,5?
CATEGORIA

plano e região

O texto é comparado diretamente. Nesta implementação didática, cada categoria é testada contra as demais.

plano = "Premium"?
idadevisitasplanoregiãocompra
221BásicoNorteNão
292PremiumNorteSim
444IntermediárioSulSim

02 · IMPUREZA DE GINI

Uma divisão é boa quando separa as classes.

Gini vale 0 quando todas as amostras do grupo pertencem à mesma classe. Quanto mais misturado estiver o grupo, maior será sua impureza.

1 · Gini do nóGini(S) = 1 − Σ pₖ²

Para 6 “Sim” e 6 “Não”:
1 − (6/12)² − (6/12)² = 0,50

→
2 · Gini da divisãoGini_div = (nₑ/n)Gini(E) + (n𝒹/n)Gini(D)

Os filhos são ponderados pela quantidade de exemplos que recebem.

→
3 · Ganhoganho = Gini_pai − Gini_div

O algoritmo escolhe o candidato com o maior ganho.

03 · LABORATÓRIO INTERATIVO

Edite a tabela e construa a árvore divisão por divisão.

As quatro características formam X; a coluna “compra” é o alvo y. Depois de qualquer alteração, treine novamente para recalcular todos os candidatos.

DIVISÕES VISÍVEIS0
DIVISÕES TOTAIS0
PROFUNDIDADE0
ACURÁCIA NO TREINO—

Dados de treinamento editáveis

Use ponto ou vírgula nos números. Plano e região aceitam texto sem qualquer codificação numérica.

idade
contínua
visitas
discreta
plano
categoria
região
categoria
compra
alvo y
Os dados serão verificados antes do treinamento.

Construção visual da árvore

Nós azuis são perguntas; nós verdes e vermelhos são previsões. Ramos ainda não abertos aparecem tracejados.

Pronta para treinar
perguntaclasse Simclasse Nãoramo ainda fechado

Gini da divisão atual, passo a passo

Veja de onde vêm os números usados para escolher a pergunta.

Nó inicial

Candidatos avaliados neste nó

A linha destacada possui o maior ganho de Gini e será usada pela árvore.

CaracterísticaPerguntaFilho “sim”Filho “não”Gini ponderadoGanho

Erro × resposta durante a construção

Cada ponto representa a árvore após abrir mais uma divisão. A linha vermelha é o erro de classificação; a verde é a acurácia.

erroacurácia

Classifique um novo exemplo

Acompanhe as perguntas percorridas da raiz até a folha.

Treine a árvore para liberar a classificação.

04 · CÓDIGO COMPLETO E EXECUTÁVEL

Treine e use uma árvore com números e textos.

Copie o programa inteiro. Ele recebe dados mistos sem codificação numérica manual, calcula candidatos e Gini, constrói a árvore, avalia o teste e classifica um novo cliente.

calcularGini()criarCandidatos()melhorDivisao()construirNo()classificar()
// ============================================================
// ÁRVORE DE DECISÃO: [idade, visitas, plano, comprou]
// Colunas 0 e 1 são numéricas; a coluna 2 contém texto.
// ============================================================
var treino = [
  [22, 1, 'Básico', 'Não'], [25, 2, 'Básico', 'Não'],
  [31, 4, 'Premium', 'Sim'], [35, 5, 'Premium', 'Sim'],
  [42, 2, 'Básico', 'Não'], [45, 6, 'Premium', 'Sim'],
  [52, 5, 'Premium', 'Sim'], [55, 1, 'Básico', 'Não']
];
var teste = [
  [28, 1, 'Básico', 'Não'], [38, 5, 'Premium', 'Sim'],
  [48, 2, 'Básico', 'Não'], [50, 6, 'Premium', 'Sim']
];
var profundidadeMaxima = 3;

function classeMajoritaria(linhas) {
  var quantidadeSim = 0;
  for (var i = 0; i < linhas.length; i++) {
    if (linhas[i][3] === 'Sim') quantidadeSim++;
  }
  if (quantidadeSim >= linhas.length - quantidadeSim) return 'Sim';
  return 'Não';
}

function calcularGini(linhas) {
  if (linhas.length === 0) return 0;
  var quantidadeSim = 0;
  for (var i = 0; i < linhas.length; i++) {
    if (linhas[i][3] === 'Sim') quantidadeSim++;
  }
  var pSim = quantidadeSim / linhas.length;
  var pNao = 1 - pSim;
  return 1 - pSim * pSim - pNao * pNao;
}

// Candidato = [coluna, tipo, valor].
function criarCandidatos(linhas) {
  var candidatos = [];
  for (var coluna = 0; coluna < 3; coluna++) {
    for (var i = 0; i < linhas.length; i++) {
      var repetido = false;
      for (var j = 0; j < candidatos.length; j++) {
        if (candidatos[j][0] === coluna && candidatos[j][2] === linhas[i][coluna]) repetido = true;
      }
      if (!repetido) {
        var tipo = coluna < 2 ? 'numero' : 'texto';
        candidatos.push([coluna, tipo, linhas[i][coluna]]);
      }
    }
  }
  return candidatos;
}

function separar(linhas, candidato) {
  var esquerda = [];
  var direita = [];
  for (var i = 0; i < linhas.length; i++) {
    var atende;
    if (candidato[1] === 'numero') atende = linhas[i][candidato[0]] <= candidato[2];
    else atende = linhas[i][candidato[0]] === candidato[2];
    if (atende) esquerda.push(linhas[i]);
    else direita.push(linhas[i]);
  }
  return [esquerda, direita];
}

// Melhor divisão = [coluna, tipo, valor, esquerda, direita, ganho].
function melhorDivisao(linhas) {
  var candidatos = criarCandidatos(linhas);
  var giniPai = calcularGini(linhas);
  var melhor = null;

  for (var i = 0; i < candidatos.length; i++) {
    var grupos = separar(linhas, candidatos[i]);
    if (grupos[0].length === 0 || grupos[1].length === 0) continue;
    var giniFilhos = (grupos[0].length * calcularGini(grupos[0])
      + grupos[1].length * calcularGini(grupos[1])) / linhas.length;
    var ganho = giniPai - giniFilhos;
    if (melhor === null || ganho > melhor[5]) {
      melhor = [candidatos[i][0], candidatos[i][1], candidatos[i][2],
        grupos[0], grupos[1], ganho];
    }
  }
  return melhor;
}

// Folha = ['folha', classe]. Nó = ['no', coluna, tipo, valor, esquerda, direita].
function construirNo(linhas, profundidade) {
  if (calcularGini(linhas) === 0 || profundidade === profundidadeMaxima) {
    return ['folha', classeMajoritaria(linhas)];
  }
  var melhor = melhorDivisao(linhas);
  if (melhor === null || melhor[5] <= 0) return ['folha', classeMajoritaria(linhas)];
  return ['no', melhor[0], melhor[1], melhor[2],
    construirNo(melhor[3], profundidade + 1),
    construirNo(melhor[4], profundidade + 1)];
}

function classificar(no, entrada) {
  if (no[0] === 'folha') return no[1];
  var atende;
  if (no[2] === 'numero') atende = entrada[no[1]] <= no[3];
  else atende = entrada[no[1]] === no[3];
  if (atende) return classificar(no[4], entrada);
  return classificar(no[5], entrada);
}

function avaliar(arvore, dados) {
  var acertos = 0;
  for (var i = 0; i < dados.length; i++) {
    if (classificar(arvore, dados[i]) === dados[i][3]) acertos++;
  }
  return acertos / dados.length;
}

// ----------------------- USO DO MODELO -----------------------
var arvore = construirNo(treino, 0);
console.log('Árvore:', JSON.stringify(arvore));
console.log('Acurácia de teste:', avaliar(arvore, teste));
console.log('Novo cliente:', classificar(arvore, [40, 5, 'Premium']));