Prevendo as notas do ENEM com Machine learning — Data Science

Wesley Watanabe
May 15 · 7 min read

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

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:

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:

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

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

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

Codenation.dev — 15/05/2019

Obrigado por ler esse artigo!

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

Wesley Watanabe