KNN
Introdução
O algoritmo K-Nearest Neighbors (KNN) foi utilizado como alternativa para prever o status de admissão dos candidatos ao MBA. Esse método classifica cada candidato com base nos perfis mais semelhantes do conjunto de treino, considerando atributos como GPA, GMAT e experiência profissional. Por sua simplicidade e flexibilidade, o KNN não depende de relações lineares e oferece uma visão baseada em proximidade entre os candidatos, funcionando como complemento às análises realizadas com a árvore de decisão.
Base de dados
A base utilizada é composta por dados sintéticos criados a partir das estatísticas da turma de 2025 do MBA de Wharton. Ela reúne informações demográficas, acadêmicas e profissionais de candidatos, como gênero, nacionalidade, área de formação, desempenho no GPA e no GMAT, além de experiência de trabalho e setor de atuação. Esses atributos foram relacionados ao status final da candidatura, categorizado como admitido, em lista de espera ou negado. Por se tratar de um conjunto de dados diversificado, é possível observar tanto os aspectos objetivos ligados ao desempenho acadêmico e profissional quanto elementos contextuais que podem influenciar o resultado do processo seletivo. Essa combinação torna o dataset especialmente relevante para análises exploratórias e para o desenvolvimento de modelos preditivos que buscam compreender os critérios implícitos de seleção em admissões de MBA.
A variável gênero apresenta uma diferença significativa na quantidade de aplicações. Observa-se uma predominância de candidatos do sexo masculino em comparação às candidatas do sexo feminino, o que indica uma distribuição desigual nesse aspecto. Essa discrepância pode refletir tendências do mercado de MBA ou características específicas da base gerada. Além disso, é um fator importante a ser considerado no modelo, já que possíveis vieses de gênero podem influenciar tanto a análise quanto as previsões de admissão.
A variável alunos internacionais mostra que a maior parte das aplicações é de candidatos domésticos (não internacionais), enquanto os estudantes internacionais representam uma parcela menor do total. Essa diferença pode indicar que os programas de MBA ainda têm maior procura local, embora o número de aplicações internacionais seja relevante para demonstrar a diversidade e a atratividade global da instituição. Essa característica pode influenciar o modelo de predição, visto que fatores como origem do aluno podem estar relacionados às taxas de aceitação.
A variável GPA apresenta distribuição concentrada em torno de valores relativamente altos, entre 3.1 e 3.3, o que indica que a maior parte dos candidatos possui desempenho acadêmico consistente. A mediana situa-se pouco acima de 3.2, reforçando esse padrão. Observa-se ainda a presença de alguns valores atípicos, tanto abaixo de 2.8 quanto acima de 3.6, que representam candidatos com desempenho fora do perfil predominante. Esses outliers, embora pouco frequentes, podem influenciar a análise estatística e devem ser considerados no pré-processamento ou na interpretação dos resultados do modelo. De forma geral, a distribuição do GPA sugere que a base é composta majoritariamente por candidatos academicamente fortes, o que pode ser um dos critérios determinantes no processo de admissão.
A variável major, que representa a área de formação acadêmica dos candidatos, apresenta distribuição relativamente equilibrada entre as categorias, mas com destaque para Humanidades, que concentra o maior número de aplicações. As áreas de STEM e Business aparecem em proporções semelhantes, ambas com menor participação em relação a Humanidades. Essa diferença pode refletir o perfil da amostra, indicando maior procura de candidatos oriundos de cursos de Humanidades pelos programas de MBA. A análise dessa variável é relevante para verificar se determinadas formações acadêmicas têm maior representatividade ou desempenham papel diferenciado nos resultados de admissão.
A variável raça apresenta distribuição diversificada entre os candidatos, com destaque para a categoria de pessoas que preferiram não se identificar, seguida pelo grupo White. Em seguida aparecem Asian, Black e Hispanic, enquanto a categoria Other concentra a menor quantidade de aplicações. Essa composição evidencia tanto a representatividade de diferentes origens raciais quanto a limitação do campo para candidatos internacionais. A análise dessa variável é importante para compreender a diversidade do conjunto de dados e avaliar se há possíveis diferenças de perfil que podem influenciar nos resultados de admissão.
A variável GMAT apresenta uma distribuição concentrada entre 600 e 700 pontos, faixa onde se encontra a maior parte dos candidatos. O pico de frequência ocorre próximo de 650 pontos, o que sugere que esse valor é representativo do desempenho médio dos aplicantes. Apesar dessa concentração, também há candidatos com pontuações mais baixas, em torno de 570, bem como outros que alcançam notas elevadas acima de 750, embora em menor quantidade. Essa distribuição indica que, em geral, os candidatos possuem desempenho sólido no exame, mas com variação suficiente para permitir que o modelo identifique padrões relacionados ao status de admissão.
A variável experiência profissional apresenta distribuição concentrada entre 4 e 6 anos de atuação no mercado, com destaque para os candidatos que possuem 5 anos de experiência, que representam a maior parte das aplicações. Os extremos da distribuição, com candidatos que possuem apenas 1 ou 2 anos de experiência e aqueles com mais de 7 anos, aparecem em menor número, configurando perfis menos frequentes na amostra. Esse padrão sugere que a base de dados está composta principalmente por profissionais em estágio intermediário de carreira, o que reflete o perfil típico de aplicantes a programas de MBA. Essa variável é particularmente relevante, pois pode influenciar diretamente nas chances de admissão, uma vez que a experiência prática é um critério valorizado nas seleções.
A variável setor de experiência profissional revela que a maior parte dos candidatos possui trajetória em Consultoria, que se destaca amplamente em relação aos demais setores. Em seguida aparecem PE/VC (Private Equity e Venture Capital), Tecnologia e setores ligados ao serviço público ou organizações sem fins lucrativos, todos com participação significativa. Áreas tradicionais como Investment Banking e Financial Services também se mostram relevantes, mas em menor proporção. Já setores como Saúde, Bens de Consumo (CPG), Mídia/Entretenimento, Varejo, Imobiliário e Energia aparecem de forma mais restrita, representando nichos específicos da amostra. Essa distribuição indica que o MBA atrai predominantemente profissionais de consultoria e finanças, mas também apresenta diversidade ao incluir candidatos de áreas emergentes e de setores menos tradicionais.
Pré-Processamento
Com base na análise exploratória realizada, foram definidos e aplicados procedimentos de pré-processamento a fim de adequar os dados para a etapa de modelagem. As principais etapas conduzidas incluem:
- Tratamento de valores ausentes:na variável admission, valores nulos foram interpretados como recusa e substituídos por Deny. Já na variável race, os valores ausentes foram preenchidos como Unknown, garantindo a consistência da base.
- Codificação de variáveis categóricas: colunas como gender, international, major, race, work_industry e admission foram convertidas em variáveis numéricas por meio do método LabelEncoder, possibilitando sua utilização pelo modelo de árvore de decisão.
- Imputação em variáveis numéricas: atributos como gpa, gmat e work_exp tiveram seus valores ausentes substituídos pela mediana, minimizando a influência de outliers e preservando a distribuição original dos dados.
- Seleção de variáveis: foram mantidas no conjunto de treino apenas as colunas relevantes para a análise preditiva, enquanto identificadores como application_id foram descartados por não possuírem valor analítico.
- Separação entre features e target: as variáveis explicativas (X) foram definidas a partir das características acadêmicas, demográficas e profissionais dos candidatos, enquanto a variável alvo (y) corresponde ao status de admission.
- Divisão em treino e teste: o conjunto de dados foi dividido em duas partes, com 80% para treino e 20% para teste, garantindo estratificação da variável alvo para preservar a proporção entre as classes.
| application_id | gender | international | gpa | major | race | gmat | work_exp | work_industry | admission |
|---|---|---|---|---|---|---|---|---|---|
| 1 | Female | False | 3.3 | Business | Asian | 620 | 3 | Financial Services | Admit |
| 2 | Male | False | 3.28 | Humanities | Black | 680 | 5 | Investment Management | nan |
| 3 | Female | True | 3.3 | Business | nan | 710 | 5 | Technology | Admit |
| 4 | Male | False | 3.47 | STEM | Black | 690 | 6 | Technology | nan |
| 5 | Male | False | 3.35 | STEM | Hispanic | 590 | 5 | Consulting | nan |
| 6 | Male | False | 3.18 | Business | White | 610 | 6 | Consulting | nan |
| 7 | Female | False | 2.93 | STEM | Other | 590 | 3 | Technology | Admit |
| 8 | Male | True | 3.02 | Business | nan | 630 | 6 | Financial Services | nan |
| 9 | Male | False | 3.24 | Business | White | 590 | 2 | Nonprofit/Gov | nan |
| 10 | Male | False | 3.27 | Humanities | Asian | 690 | 3 | Consulting | nan |
| 11 | Male | False | 3.05 | Humanities | White | 580 | 5 | Technology | nan |
| 12 | Male | True | 2.85 | Humanities | nan | 580 | 4 | PE/VC | nan |
| 13 | Female | False | 3.24 | Humanities | Hispanic | 640 | 6 | PE/VC | Waitlist |
| 14 | Female | False | 3.39 | Business | Black | 690 | 4 | Nonprofit/Gov | nan |
| 15 | Female | False | 3.03 | STEM | White | 600 | 5 | Technology | Admit |
| 16 | Female | True | 3.05 | Humanities | nan | 710 | 4 | Consulting | Admit |
| 17 | Female | False | 3.32 | Business | Asian | 710 | 5 | PE/VC | Admit |
| 18 | Male | False | 3.23 | Humanities | Black | 700 | 4 | Health Care | nan |
| 19 | Male | False | 3.13 | Humanities | White | 630 | 6 | Financial Services | nan |
| 20 | Male | True | 3.09 | Business | nan | 670 | 8 | Consulting | nan |
| gender | international | gpa | major | race | gmat | work_exp | work_industry | admission |
|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 3.48 | 1 | 5 | 630 | 5 | 8 | 0 |
| 1 | 0 | 3.27 | 0 | 5 | 610 | 6 | 10 | 0 |
| 1 | 0 | 3.31 | 2 | 0 | 670 | 6 | 8 | 0 |
| 1 | 1 | 3.29 | 1 | 4 | 660 | 5 | 10 | 0 |
| 1 | 1 | 3.02 | 2 | 4 | 570 | 3 | 10 | 0 |
| 1 | 0 | 3.4 | 1 | 0 | 710 | 4 | 13 | 0 |
| 0 | 0 | 3.23 | 0 | 0 | 670 | 7 | 9 | 0 |
| 1 | 0 | 3.36 | 2 | 1 | 700 | 5 | 10 | 2 |
| 0 | 0 | 3.36 | 1 | 1 | 670 | 4 | 6 | 2 |
| 1 | 0 | 3.29 | 0 | 5 | 620 | 6 | 1 | 0 |
| 1 | 1 | 3.4 | 2 | 4 | 580 | 5 | 1 | 0 |
| 0 | 0 | 3.21 | 1 | 2 | 580 | 5 | 1 | 0 |
| 1 | 1 | 3.06 | 0 | 4 | 640 | 4 | 1 | 0 |
| 0 | 0 | 3.2 | 1 | 2 | 630 | 6 | 1 | 0 |
| 1 | 0 | 3.25 | 2 | 0 | 610 | 5 | 6 | 0 |
| 1 | 0 | 3.27 | 2 | 1 | 630 | 6 | 1 | 0 |
| 1 | 1 | 3.01 | 0 | 4 | 610 | 6 | 13 | 0 |
| 1 | 1 | 3.15 | 0 | 4 | 620 | 6 | 10 | 0 |
| 0 | 0 | 2.93 | 1 | 2 | 580 | 5 | 0 | 0 |
| 1 | 1 | 3.25 | 0 | 4 | 570 | 6 | 8 | 0 |
Divisão dos Dados
Após o pré-processamento, os dados foram divididos em dois subconjuntos: 80% para treinamento e 20% para teste. Essa proporção é adequada em problemas de classificação, pois garante exemplos suficientes para o aprendizado do modelo, ao mesmo tempo em que reserva uma amostra representativa para a avaliação final.
A divisão foi realizada utilizando a função train_test_split da biblioteca scikit-learn, com estratificação pela variável alvo (admission), de modo a preservar a proporção original entre as classes (Admit, Waitlist e Deny) em ambos os conjuntos. Isso assegura que o modelo não seja influenciado por distribuições desbalanceadas.
Dessa forma, o conjunto de treinamento é utilizado para armazenar os exemplos que servirão de referência ao algoritmo KNN, enquanto o conjunto de teste é aplicado para medir a acurácia e a capacidade de generalização do modelo na classificação de novos candidatos.
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
df = pd.read_csv("./src/MBA.csv")
label_encoder = LabelEncoder()
df["admission"] = df["admission"].fillna("Deny")
df["race"] = df["race"].fillna("Unknown")
df["gender"] = label_encoder.fit_transform(df["gender"])
df["international"] = label_encoder.fit_transform(df["international"])
df["major"] = label_encoder.fit_transform(df["major"])
df["race"] = label_encoder.fit_transform(df["race"])
df["work_industry"] = label_encoder.fit_transform(df["work_industry"])
df["admission"] = label_encoder.fit_transform(df["admission"])
x = df[[
"gender", "international", "gpa", "major",
"race", "gmat", "work_exp", "work_industry"
]]
y = df["admission"]
x_train, x_test, y_train, y_test = train_test_split(
x, y, test_size=0.3, random_state=27, stratify=y
)
print("Dimensão do treino:", x_train.shape)
print("Dimensão do teste:", x_test.shape)
Treinamento do Modelo
Acurácia (KNN k=5): 0.84 Acurácia (KNN k=5): 0.84 [SALVO] Artefatos de avaliação do KNN em docs/knn/artifacts/knn_eval.pkl
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
DATA_PATH = "./src/MBA.csv"
df = pd.read_csv(DATA_PATH)
df["admission"] = df["admission"].fillna("Deny")
df["race"] = df["race"].fillna("Unknown")
adm_map = {"Deny": 0, "Waitlist": 1, "Admit": 2}
y = df["admission"].map(adm_map).astype(int)
num_cols = ["gpa", "gmat", "work_exp"]
cat_cols = ["gender", "international", "major", "race", "work_industry"]
for c in num_cols:
df[c] = pd.to_numeric(df[c], errors="coerce")
df[c] = df[c].fillna(df[c].median())
X_cat = pd.get_dummies(df[cat_cols].astype(str).apply(lambda s: s.str.strip()),
drop_first=False, dtype=int)
X_num = df[num_cols].copy()
scaler = StandardScaler()
X_num[num_cols] = scaler.fit_transform(X_num[num_cols])
X = pd.concat([X_num, X_cat], axis=1).values
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.20, random_state=42, stratify=y
)
class KNNClassifier:
def __init__(self, k=5):
self.k = k
def fit(self, X, y):
self.X_train = X
self.y_train = np.array(y)
def predict(self, X):
return np.array([self._predict(x) for x in X])
def _predict(self, x):
distances = np.sqrt(((self.X_train - x) ** 2).sum(axis=1))
k_idx = np.argsort(distances)[:self.k]
k_labels = self.y_train[k_idx]
vals, counts = np.unique(k_labels, return_counts=True)
return vals[np.argmax(counts)]
knn = KNNClassifier(k=5)
knn.fit(X_train, y_train)
y_pred = knn.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"Acurácia (KNN k={knn.k}): {acc:.2f}")
y_pred = knn.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"Acurácia (KNN k={knn.k}): {acc:.2f}")
# --- salvar artefatos do KNN (somente arrays) ---
import os, joblib
ART = "docs/knn/artifacts"
os.makedirs(ART, exist_ok=True)
# salve SOMENTE dados numéricos usados na avaliação
joblib.dump(
{
"X_test": X_test,
"y_test": y_test,
"y_pred": y_pred,
},
f"{ART}/knn_eval.pkl", compress=3
)
# (opcional) se quiser salvar o modelo também, salve em separado.
# Evite salvar o KNN customizado; use scikit-learn KNeighborsClassifier se precisar recarregar.
# joblib.dump(knn, f"{ART}/knn_model.pkl", compress=3)
print(f"[SALVO] Artefatos de avaliação do KNN em {ART}/knn_eval.pkl")
Usando Scikit-Learn
Accuracy: 0.83
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from io import StringIO
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
df = pd.read_csv("./src/MBA.csv")
df["admission"] = df["admission"].fillna("Deny")
df["race"] = df["race"].fillna("Unknown")
adm_map = {"Deny": 0, "Waitlist": 1, "Admit": 2}
y = df["admission"].map(adm_map).astype(int)
num_cols = ["gpa", "gmat", "work_exp"]
cat_cols = ["gender", "international", "major", "race", "work_industry"]
for c in num_cols:
df[c] = pd.to_numeric(df[c], errors="coerce")
df[c] = df[c].fillna(df[c].median())
X_cat = pd.get_dummies(df[cat_cols].astype(str).apply(lambda s: s.str.strip()),
drop_first=False, dtype=int)
X_num = df[num_cols].copy()
scaler = StandardScaler()
X_num[num_cols] = scaler.fit_transform(X_num[num_cols])
X = pd.concat([X_num, X_cat], axis=1).values
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
pca = PCA(n_components=2)
X_train_2d = pca.fit_transform(X_train)
X_test_2d = pca.transform(X_test)
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train_2d, y_train)
predictions = knn.predict(X_test_2d)
print(f"Accuracy: {accuracy_score(y_test, predictions):.2f}")
plt.figure(figsize=(12, 10))
h = 0.05
x_min, x_max = X_train_2d[:, 0].min() - 1, X_train_2d[:, 0].max() + 1
y_min, y_max = X_train_2d[:, 1].min() - 1, X_train_2d[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = knn.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, cmap=plt.cm.RdYlBu, alpha=0.3)
sns.scatterplot(x=X_train_2d[:, 0], y=X_train_2d[:, 1], hue=y_train,
palette="deep", s=100, edgecolor="k", alpha=0.8)
plt.xlabel("PCA 1")
plt.ylabel("PCA 2")
plt.title("KNN Decision Boundary (MBA Dataset)")
buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())
Avaliação do Modelo
O modelo KNN, configurado com k = 5, apresentou uma acurácia de 84% no conjunto de teste, evidenciando bom desempenho na tarefa de prever o status de admissão dos candidatos. Isso significa que, em média, oito a cada dez previsões realizadas foram corretas. A visualização da fronteira de decisão confirma esse resultado: a maior parte do espaço é dominada pela classe Deny, refletindo a predominância dessa categoria na base. Ainda assim, o modelo conseguiu identificar regiões específicas associadas às classes Waitlist e Admit, ainda que com certa sobreposição, o que explica eventuais erros de classificação.
O desempenho consistente do KNN reforça sua capacidade de capturar padrões de similaridade entre os candidatos, especialmente quando atributos como GPA e GMAT se combinam com variáveis de experiência profissional. Entretanto, a irregularidade da fronteira de decisão também evidencia uma limitação do método, que pode se tornar sensível a ruídos e depender fortemente da distribuição dos dados no espaço de features.
Conclusão
O uso do algoritmo KNN mostrou-se eficaz para a classificação dos candidatos ao MBA, atingindo bons níveis de acurácia e confirmando a relevância de atributos acadêmicos e profissionais na determinação do status de admissão. O modelo se destacou por sua simplicidade e pela interpretação intuitiva baseada em proximidade entre perfis semelhantes.
Apesar disso, a análise gráfica revelou que a separação entre classes não é totalmente nítida, em especial entre os grupos Admit e Waitlist, o que sugere a necessidade de ajustes no valor de k ou a utilização de técnicas complementares para melhorar a robustez das previsões. De maneira geral, os resultados demonstram que o KNN pode servir como uma ferramenta útil no apoio à análise de admissões, oferecendo uma abordagem alternativa e comparativa em relação à árvore de decisão, já aplicada anteriormente.