Prevendo as notas do ENEM com Machine learning — Data Science
Criando um modelo preditivo com Python, Scikit learn e Jupyter/Colab.
Calma calma! Se você caiu aqui com a ideia que ia ser dar bem no ENEM sem estudar não é essa a idea! Mas se você já conhece ou ouvir falar sobre Machine Learning sabe que a chamada desse artigo é pouco sensacionalista.
Mas sim, é possível ‘prever’ as notas, e isso que o Codenation propõe num desafio no seu site, no caso a nota da prova de matemática.
Aproveitando para comentar, lá não possui somente desafios de Data Science, nesse momento existem mais dois desafios acontecendo: ‘JAVA’ e ‘Python para Web’. Para saber mais sobre esses desafios e o programa de aceleração de carreira acesse o site do Codenation.
Lembrando que o propósito deste artigo é:
- Apresentar o desafio.
- Mostrar algumas análises feitas.
- Dicas
Vamos ao que interessa !
1 - Iniciando:
É necessário instalar várias coisas, porém para facilitar a nossa vida podemos instalar o ANACONDA, com ele será instalado as bibliotecas necessárias, o Python e o Jupyter Notebook.
Ou mais fácil ainda, podemos usar o Colab do Google, onde é necessário somente uma conta Google para ter um “Jupyter notebook” no navegador.
2 - Entendendo o nosso problema:
O contexto do desafio gira em torno dos resultados do ENEM 2016 (disponíveis no arquivo train.csv). Este arquivo, e apenas ele, deve ser utilizado para todos os desafios. Qualquer dúvida a respeito das colunas, consulte o Dicionário dos Microdados do Enem 2016.
Muitas universidades brasileiras utilizam o ENEM para selecionar seus futuros alunos e alunas. Isto é feito com uma média ponderada das notas das provas de matemática, ciências da natureza, linguagens e códigos, ciências humanas e redação, com os pesos abaixo:
matemática: 3
ciências da natureza: 2
linguagens e códigos: 1.5
ciências humanas: 1
redação: 3
No arquivo test.csv crie um modelo para prever nota da prova de matemática (coluna NU_NOTA_MT) de quem participou do ENEM 2016.
Salve sua resposta em um arquivo chamado answer.csv com duas colunas: NU_INSCRICAO e NU_NOTA_MT.
3 — Mão na massa
Importando os dados com o Colab:
import numpy as np
import pandas as pd
from sklearn import metricsdf_train = pd.read_csv('train.csv', sep="," , encoding="UTF8" )
df_test = pd.read_csv('test.csv', sep="," , encoding="UTF8" )
Quais colunas temos na base de test para trabalhar ?
df_test.columns
Vamos verificar quais Features tem uma maior correlação com o nosso target (NU_NOTA_MT):
df_train.corr()
As features com maiores correlações em relação ao nosso Target (NU_NOTA_MT) serão as melhores a serem usadas, porém somente a seleção delas não vai garantir que sua análise seja satisfatória. De qualquer forma, vamos se atentar a selecionar as melhores nesse momento.
Verificar como estão as distribuições:
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('ggplot')
%matplotlib inlinefeatures = [
'NU_NOTA_CN',
'NU_NOTA_CH',
'NU_NOTA_LC',
'NU_NOTA_REDACAO',
'NU_NOTA_COMP1',
'NU_NOTA_COMP2',
'NU_NOTA_COMP3',
'NU_NOTA_COMP4',
'NU_NOTA_COMP5']features_corr = [
'NU_NOTA_MT',
'NU_NOTA_CN',
'NU_NOTA_CH',
'NU_NOTA_LC',
'NU_NOTA_REDACAO',
'NU_NOTA_COMP1',
'NU_NOTA_COMP2',
'NU_NOTA_COMP3',
'NU_NOTA_COMP4',
'NU_NOTA_COMP5']
Verificando os valores nulos na base de treino:
df_train[features].isnull().sum()
Como temos bastante valores nulls será necessário trabalhar com eles.
Criando um heatmap das Features incluindo NU_NOTA_MT para vermos as nossas correlações:
corr = df_train[features_corr].corr()
ax = plt.subplots(figsize=(11, 8))
sns.heatmap(corr, annot=True, annot_kws={"size": 10})
Criaremos alguns gráficos para ver a distribuição dos dados:
x0 = df_train['NU_NOTA_CN'].fillna(0)
x1 = df_test['NU_NOTA_CN'].fillna(0)sns.distplot(x0)
sns.distplot(x1)
plt.legend(labels=['TRAIN','TEST'], ncol=2, loc='upper left');
Como podemos ver há uma grande parte de notas zeradas pois colocamos os valores null como 0.
O que vai ser feito daqui para frente fica a critério do cientista de dados, entre as alternativas são:
- Aplicar a média nas notas.
- Deixar zerado.
- Remover do dataset de treino esse valores.
Todas essa opções vão gerar resultados diferentes do dataset. A minha escolha nesse problema foi remover essas notas do dataset de treino. Dessa forma meu modelo vai ser treinado numa base com distribuição ‘normal’.
Para a composição da nota de redação vou aplicar o valor 0, devido ele não ter atingido ao critério, a nota é zero mesmo.
Obs: Todos esses assumptions já estão sendo feitos após eu ler exaustivamente o dicionário de dados, o qual eu recomendo fortemente que você faça antes de começar qualquer análise em um dataset (caso odicionário exista).
# Seleciona somente linhas com valores nesses 4 quesitos a baixo na base de train
df_train = df_train.loc[
(df_train['NU_NOTA_CN'].notnull()) & (df_train['NU_NOTA_CN'] != 0) & (df_train['NU_NOTA_CH'].notnull()) & (df_train['NU_NOTA_CH'] != 0)
& (df_train['NU_NOTA_LC'].notnull()) & (df_train['NU_NOTA_LC'] != 0) & (df_train['NU_NOTA_REDACAO'].notnull()) & (df_train['NU_NOTA_REDACAO'] != 0)
]
Vamos verificar os gráficos novamente:
Insight: as provas do ENEM são feitas em dois dias, e nesses dois dias 2 cadernos são usados. A nota que estamos tentando estimar aqui é a nota de Matemática, sabendo disso se pegarmos o campo ‘TP_PRESENCA_LC’ o qual confirma ou não a presença do aluno na prova, pode ser muito útil na nossa análise.
Podemos assumir que se o aluno não estava presente em “Linguagens, Códigos e suas Tecnologias”, também não estava presente na de Matemática, a qual é no mesmo dia.
Dessa forma você pode separar os dados, e assumir todas aquelas inscrições obtiveram a nota zero em Matemática.
Selecionando somente valores diferentes de 0 e não nulos na base de test:
df_test = df_test.loc[
(df_test['NU_NOTA_CN'].notnull()) & (df_test['NU_NOTA_CN'] != 0) & (df_test['NU_NOTA_CH'].notnull()) & (df_test['NU_NOTA_CH'] != 0)
& (df_test['NU_NOTA_LC'].notnull()) & (df_test['NU_NOTA_LC'] != 0) & (df_test['NU_NOTA_REDACAO'].notnull()) & (df_test['NU_NOTA_REDACAO'] != 0)
]
Verificando a quantidade de notas ‘nulls’ na base de test:
df_test[features].isnull().sum()
Agora sim! os dados parecem bons para usar no nosso modelo, vamos gerar os gráficos novamente.
Como podemos observar, agora temos dados muito mais homogêneos entre teste e treino do que quando iniciamos a nossa análise.
Criando o modelo de regressão
Zerando os campos nulos de composição da nota de redação:
df_test['NU_NOTA_COMP1'].fillna(0,inplace=True)
df_test['NU_NOTA_COMP2'].fillna(0,inplace=True)
df_test['NU_NOTA_COMP3'].fillna(0,inplace=True)
df_test['NU_NOTA_COMP4'].fillna(0,inplace=True)
df_test['NU_NOTA_COMP5'].fillna(0,inplace=True)df_train['NU_NOTA_COMP1'].fillna(0,inplace=True)
df_train['NU_NOTA_COMP2'].fillna(0,inplace=True)
df_train['NU_NOTA_COMP3'].fillna(0,inplace=True)
df_train['NU_NOTA_COMP4'].fillna(0,inplace=True)
df_train['NU_NOTA_COMP5'].fillna(0,inplace=True)
Lembrando que selecionei essas Features para exemplificar o nosso modelo, você pode e deve fazer os seus testes. Selecionar/Testar/Criar faz parte do processo, e isso é o que toma mais parte da análise geralmente.
Seguindo:
- Atribuir y_train ao meu target
- Setar x test e x train com as features selecionadas
- Normalizar os dados
y_train = df_train['NU_NOTA_MT']
x_train = df_train[features]
x_test = df_test[features]from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
x_train = sc.fit_transform(x_train)
x_test = sc.transform(x_test)
O modelo que usei para fazer a regressão, no caso foi o RandomForestRegressor. Mais informações sobre o modelo e os parâmetros você pode ver na documentação oficial.
from sklearn.ensemble import RandomForestRegressor
regressor = RandomForestRegressor(
criterion='mae',
max_depth=8,
max_leaf_nodes=None,
min_impurity_split=None,
min_samples_leaf=1,
min_samples_split=2,
min_weight_fraction_leaf=0.0,
n_estimators= 500,
n_jobs=-1,
random_state=0,
verbose=0,
warm_start=False
)
Abaixo um gráfico de problemas/algoritmos apenas para exemplificar:
Treinando o nosso modelo através do fit:
regressor.fit(x_train, y_train)
Realizando a predição das notas da nossa base test:
y_pred_test = regressor.predict(x_test)#y_pred_train = regressor.predict(x_train)
O Codenation não disponibiliza ou comenta qual o meio que o sistema avalia as notas. Dessa forma recomendo você criar o seu próprio método de avaliação e comparar com o score de submissão.
Eu usei o MAE, MSE E RSME do metrics para analisar, você pode achar a documentação aqui.
print('MAE:', metrics.mean_absolute_error(y_train, y_pred_train).round(8) )
print('MSE:', metrics.mean_squared_error(y_train, y_pred_train).round(8) )
print('RMSE:', np.sqrt(metrics.mean_squared_error(y_train, y_pred_train)).round(8))
Conclusão
O propósito aqui é estimular o pensamento analítico e exploratório dos dados. Tem alguns conceitos que não foram abordados, mas podem ser úteis em análises desse tipo.
- O Feature engineering, nada mais é a criação de features a partir de outras.
- AutoML’s: Google, IBM e Amazon tem seus próprios serviços de Machine Learning, onde prometem (Já entregam) muita facilidade em gerar modelos.
Links
- Codenation — https://www.codenation.dev
- Google Colab — https://colab.research.google.com
- Anaconda — https://anaconda.org
- Feature Engineering — https://towardsdatascience.com/automated-feature-engineering-in-python-99baf11cc219
Minha participação no desafio
O desafio do Codenation me estimulou a pensar fora da caixa, pensar em alternativas de features, aprender bibliotecas novas, testar n modelos e pesquisar muito! Consegui uma posição final de 6º com um score de 93.86% , o qual eu achei muito bacana para uma primeira participação e pela quantidade de participantes (pelo menos 70 no ranking).
Obrigado por ler esse artigo!
Você pode me achar no Linkedin: https://www.linkedin.com/in/wesleywatanabe/
Wesley Watanabe