Prevendo as notas do ENEM com Machine learning — Data Science

Criando um modelo preditivo com Python, Scikit learn e Jupyter/Colab.

Wesley Watanabe
7 min readMay 15, 2019
Imagem da internet

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 metrics
df_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 inline
features = [
'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:

imagem da internet

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

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 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).

Codenation.dev — 15/05/2019

Obrigado por ler esse artigo!

Você pode me achar no Linkedin: https://www.linkedin.com/in/wesleywatanabe/

Wesley Watanabe

--

--