Classificação de Estresse Acadêmico com KNN
Exploração dos Dados
A base possui 280 registros e 9 colunas.
Estágio acadêmico: Refere-se ao nível acadêmico do respondente (graduação, ensino médio, pós-graduação).
Pressão dos colegas: O quanto o aluno se sente pressionado pelos colegas em uma escala de 1 a 5.
Pressão acadêmica da família: O quanto o aluno se sente pressionado pela família em uma escala de 1 a 5.
Ambiente de estudo: Como o aluno classifica o ambiente onde estuda (categorias como Peaceful, Noisy, Disrupted).
Estratégia de enfrentamento: Como o aluno enfrenta o estresse (ex.: Analisar a situação com inteligência, Colapso emocional, Apoio de amigos/família).
Maus hábitos: Indica se o respondente possui hábitos nocivos, como fumar ou beber (Sim/Não/Prefiro não responder).
Competição acadêmica: Grau de competição acadêmica percebida (escala de 1 a 5).
Índice de estresse: Nível de estresse acadêmico (escala de 1 a 5).
| AcademicStage | PeerPressure | HomePressure | StudyEnv | Strategy | BadHabits | AcademicComp | Stress |
|---|---|---|---|---|---|---|---|
| undergraduate | 4 | 5 | Noisy | Analyze the situation and handle it with intellect | No | 3 | 5 |
| undergraduate | 3 | 4 | Peaceful | Analyze the situation and handle it with intellect | No | 3 | 3 |
| undergraduate | 1 | 1 | Peaceful | Social support (friends, family) | No | 2 | 4 |
| undergraduate | 3 | 2 | Peaceful | Analyze the situation and handle it with intellect | No | 4 | 3 |
| undergraduate | 3 | 3 | Peaceful | Analyze the situation and handle it with intellect | No | 4 | 5 |
Pré-processamento
Remoção de colunas irrelevantes
A coluna Timestamp foi removida por não agregar informações para a previsão.
Variável-alvo
A variável alvo é Stress. Por problemas na acuracia do modelo e após alguns testes, decidi que ao invés de utilizar 5 grupos, um para cada tipo de stress, dividi em apenas 2, onde 1 o nivel é abaixo de 3 e 0, onde o nível é 3 ou maior
Tratamento de valores ausentes
Foi identificado apenas um valor nulo em StudyEnv (ambiente de estudo), que foi preenchido com o valor mais frequente (Peaceful).
Codificação de variáveis categóricas
As colunas AcademicStage, StudyEnv, Strategy e BadHabits são categóricas e foram convertidas para valores numéricos utilizando Label Encoding.
Features e target
features (X): estágio acadêmico, pressão dos colegas, pressão da família, ambiente de estudo, estratégia de enfrentamento, hábitos nocivos, nível de competição acadêmica.
target (y): índice de estresse acadêmico.
Divisão dos Dados
Os dados foram divididos de forma estratificada em 80% treino e 20% teste, garantindo que as proporções entre as classes de estresse fossem preservadas.
Treinamento do Modelo
Accuracy: 0.82
Feature Importances (Permutation):
| Feature | Importance | Std |
|---|---|---|
| AcademicComp | 0.097024 | 0.040111 |
| HomePressure | 0.040476 | 0.032581 |
| AcademicStage | 0.036905 | 0.020585 |
| BadHabits | 0.016667 | 0.034173 |
| PeerPressure | 0.011905 | 0.025394 |
| StudyEnv | 0.002381 | 0.019956 |
| Strategy | -0.001786 | 0.016846 |
import numpy as np
import matplotlib.pyplot as plt
from io import StringIO
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import seaborn as sns
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.inspection import permutation_importance
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from itertools import cycle
plt.figure(figsize=(12, 10))
# Preprocess the data
def preprocess(df: pd.DataFrame) -> pd.DataFrame:
# remocao da coluna Timestamp
df = df.drop(columns=['Timestamp'])
# Tratamento de missing values
## 'Study Environment' tem 1 valor ausente -> preenchid com a moda (valor mais frequente)
df['StudyEnv'] = df['StudyEnv'].fillna(df['StudyEnv'].mode().iloc[0])
#conversao de variaveis categoricas
label_encoder = LabelEncoder()
df['AcademicStage'] = label_encoder.fit_transform(df['AcademicStage'])
df['StudyEnv'] = label_encoder.fit_transform(df['StudyEnv'])
df['Strategy'] = label_encoder.fit_transform(df['Strategy'])
df['BadHabits'] = label_encoder.fit_transform(df['BadHabits'])
# Selecao de features
features = [
'AcademicStage',
'PeerPressure',
'HomePressure',
'StudyEnv',
'Strategy',
'BadHabits',
'AcademicComp'
]
return df[features]
# Carregar base
df = pd.read_csv('https://raw.githubusercontent.com/tigasparzin/Machine-Learning/refs/heads/main/data/StressExp.csv')
d = preprocess(df.copy())
X = d[['AcademicStage','PeerPressure',
'HomePressure',
'StudyEnv',
'Strategy',
'BadHabits',
'AcademicComp']]
# alvo binário: baixo (<3) e alto (>=3)
y = (df['Stress'] >= 3).map({False: 1, True: 0})
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Train KNN model
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
predictions = knn.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, predictions):.2f}")
r = permutation_importance(
knn,
X_test,
y_test,
n_repeats=30,
random_state=42,
scoring='accuracy'
)
feature_importance = pd.DataFrame({
'Feature': X.columns,
'Importance': r.importances_mean,
'Std': r.importances_std
})
report_dict = classification_report(y_test, predictions, output_dict=True)
report_df = pd.DataFrame(report_dict).transpose()
cm = confusion_matrix(y_test, predictions)
labels = knn.classes_
cm_df = pd.DataFrame(cm, index=labels, columns=labels)
# ordenar e mostrar (HTML igual ao seu exemplo)
feature_importance = feature_importance.sort_values(by='Importance', ascending=False)
print("<br>Feature Importances (Permutation):")
print(feature_importance.to_html(index=False))
# print("<h3>Relatório de Classificação:</h3>")
# print(report_df.to_html(classes="table table-bordered table-striped", border=0))
# Escalar features
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Reduzir para 2 dimensões (apenas para visualização)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
# Split train/test
X_train, X_test, y_train, y_test = train_test_split(X_pca, y, test_size=0.3, random_state=42)
# Treinar KNN
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
predictions = knn.predict(X_test)
# Visualize decision boundary
h = 0.02 # Step size in mesh
x_min, x_max = X_pca[:, 0].min() - 1, X_pca[:, 0].max() + 1
y_min, y_max = X_pca[:, 1].min() - 1, X_pca[:, 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_pca[:, 0], y=X_pca[:, 1], hue=y, style=y, palette="deep", s=100)
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.title("KNN Decision Boundary (k=3)")
# Display the plot
buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())
Avaliação do Modelo
Accuracy: 0.82
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from io import StringIO
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
# carregar os dados
df = pd.read_csv('https://raw.githubusercontent.com/tigasparzin/Machine-Learning/refs/heads/main/data/StressExp.csv')
# Preprocess the data
def preprocess(df: pd.DataFrame) -> pd.DataFrame:
# remocao da coluna Timestamp
df = df.drop(columns=['Timestamp'])
# Tratamento de missing values
## 'Study Environment' tem 1 valor ausente -> preenchid com a moda (valor mais frequente)
df['StudyEnv'] = df['StudyEnv'].fillna(df['StudyEnv'].mode().iloc[0])
#conversao de variaveis categoricas
label_encoder = LabelEncoder()
df['AcademicStage'] = label_encoder.fit_transform(df['AcademicStage'])
df['StudyEnv'] = label_encoder.fit_transform(df['StudyEnv'])
df['Strategy'] = label_encoder.fit_transform(df['Strategy'])
df['BadHabits'] = label_encoder.fit_transform(df['BadHabits'])
# Selecao de features
features = [
'AcademicStage',
'PeerPressure',
'HomePressure',
'StudyEnv',
'Strategy',
'BadHabits',
'AcademicComp'
]
return df[features]
d = preprocess(df.copy())
X = d[['AcademicStage','PeerPressure',
'HomePressure',
'StudyEnv',
'Strategy',
'BadHabits',
'AcademicComp']]
# alvo binário: baixo (<3) e alto (>=3)
y = (df['Stress'] >= 3).map({False: 1, True: 0})
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Train KNN model
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
predictions = knn.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, predictions):.2f}")
# gerar matriz de confusão
labels = np.sort(np.unique(np.concatenate([y_test, predictions])))
cm = confusion_matrix(y_test, predictions, labels=labels)
# plotar matriz de confusão
fig, ax = plt.subplots(figsize=(8, 6))
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels)
disp.plot(ax=ax, cmap=plt.cm.Blues, values_format="d", colorbar=False)
ax.set_title("Matriz de Confusão - KNN")
ax.set_xlabel("Previsto")
ax.set_ylabel("Real")
# exportar para SVG
buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True, bbox_inches="tight")
svg_text = buffer.getvalue()
print(svg_text)
plt.close(fig)
Sobre binarização no alvo
Durante os primeiros modelos de teste, com os 5 niveis de estresse, estava tendo resultados com acuracias como 52% e após mudanças no modelo para tentar aumentar esse valor, chegando até no maximo 65%, então percebi que o erro poderia estar, na quantidade, assim tive a ideia de classificar os niveis de estresse como altos (=< 3) e baixos(< 3), assim chegando em uma acuracia de 82%.
Conclusão
O uso do KNN nesta base evidenciou pontos importantes para o aprendizado:
-
O desbalanceamento das classes afeta a performance: níveis baixos de estresse (1 e 2) são pouco representados, o que dificulta acertos nessas classes.
-
O modelo se mostrou sensível à escolha das variáveis de entrada e ao valor de k. Testes com diferentes combinações podem alterar significativamente a acurácia.
-
A normalização/standardização das variáveis numéricas é fundamental para que todas as features tenham peso similar na distância euclidiana usada pelo KNN.
-
Técnicas como binarização do alvo (baixo vs. alto estresse) podem ser boas alternativas dependendo do objetivo da análise.
Assim, o projeto reforça a importância do pré-processamento cuidadoso, da atenção ao balanceamento de classes e da análise crítica dos resultados para que o modelo seja útil de acordo com o problema real.