Laboratório 3 - Parte 3
Prevendo evasão
Este documento é referente a continuação do Lab 3. Nesta parte 3 o objetivo é utilizar os dados anteriores e melhorar o modelo para tentar prever quais alunos irão evadir.
Continuaremos utilizando o modelo de árvore de decisão, já que foi o que teve melhor resultado na parte 2. Utilizamos como dados de treino um recorte dos dados fornecidos, considerando os períodos a partir de 2011 e deixando o último período para utilizar como dados de teste.
dados = read.csv("lab3_kaggle_classificacao_treino.csv")
dados.teste.final = read.csv("lab3_kaggle_classificacao_teste.csv")
mat_unicas = dados[!duplicated(dados[,1]),]
contagem.distr = group_by(mat_unicas, MAT_TUR_ANO, EVADIU) %>% summarise(count = n())
dados.treino = subset(dados, MAT_TUR_ANO > 2010 & (MAT_TUR_ANO < 2015))
dados.treino = rbind(dados.treino, subset(dados, MAT_TUR_ANO == 2015 & MAT_TUR_PERIODO == 1))
dados.teste = subset(dados, MAT_TUR_ANO == 2015 & MAT_TUR_PERIODO == 2)
dados.treino = dados.treino %>% select(-MAT_TUR_DIS_DISCIPLINA, -MAT_TUR_ANO, -MAT_TUR_PERIODO) %>% filter(!is.na(MAT_MEDIA_FINAL))
dados.teste = dados.teste %>% select(-MAT_TUR_DIS_DISCIPLINA, -MAT_TUR_ANO, -MAT_TUR_PERIODO) %>% filter(!is.na(MAT_MEDIA_FINAL))
dados.teste.final = dados.teste.final %>% select(-MAT_TUR_DIS_DISCIPLINA, -MAT_TUR_ANO, -MAT_TUR_PERIODO)
alunos.evadiu.treino = dados.treino %>%
group_by(MAT_ALU_MATRICULA) %>%
summarise(EVADIU = any(EVADIU))
alunos.evadiu.teste = dados.teste %>%
group_by(MAT_ALU_MATRICULA) %>%
summarise(EVADIU = any(EVADIU))
alunos.evadiu.teste.final = dados.teste.final %>%
group_by(MAT_ALU_MATRICULA) %>%
summarise()
dados.treino = dados.treino %>%
group_by(MAT_ALU_MATRICULA, DISCIPLINA) %>%
filter(MAT_MEDIA_FINAL == max(MAT_MEDIA_FINAL)) %>%
ungroup() %>%
select(MAT_ALU_MATRICULA, DISCIPLINA, MAT_MEDIA_FINAL) %>%
mutate(DISCIPLINA = as.factor(gsub(" ",".",DISCIPLINA))) %>%
dcast(MAT_ALU_MATRICULA ~ DISCIPLINA, mean) %>%
merge(alunos.evadiu.treino)
dados.teste = dados.teste %>%
group_by(MAT_ALU_MATRICULA, DISCIPLINA) %>%
filter(MAT_MEDIA_FINAL == max(MAT_MEDIA_FINAL)) %>%
ungroup() %>%
select(MAT_ALU_MATRICULA, DISCIPLINA, MAT_MEDIA_FINAL) %>%
mutate(DISCIPLINA = as.factor(gsub(" ",".",DISCIPLINA))) %>%
dcast(MAT_ALU_MATRICULA ~ DISCIPLINA, mean) %>%
merge(alunos.evadiu.teste)
dados.teste.final = dados.teste.final %>%
group_by(MAT_ALU_MATRICULA, DISCIPLINA) %>%
ungroup() %>%
select(MAT_ALU_MATRICULA, DISCIPLINA, MAT_MEDIA_FINAL) %>%
mutate(DISCIPLINA = as.factor(gsub(" ",".",DISCIPLINA))) %>%
dcast(MAT_ALU_MATRICULA ~ DISCIPLINA, mean) %>%
merge(alunos.evadiu.teste.final)Melhorando o modelo
A abordagem anterior utilizada foi uso das médias das disciplinas como atributos e a adição de um novo atributo, referente se o aluno reprovou alguma das disciplinas de programação. Para a inputação de dados, foi feita uma média das notas presentes de um aluno e colocadas onde havia NA.
Nessa parte, temos duas diferenças: adicionamos um novo atributo que é referente a quantidade de NA’s que um aluno possui e no momento de inputação, se um aluno só possui NA’s, todas as suas notas são substituídas por zero.
dados.treino$nNA = NA
dados.teste$nNA = NA
dados.teste.final$nNA = NA
for(i in 1:nrow(dados.treino)){
dados.treino[i,]$nNA = sum(is.na(dados.treino[i,2:7]))
if(i <= nrow(dados.teste)){
dados.teste[i,]$nNA = sum(is.na(dados.teste[i,2:7]))
}
if(i <= nrow(dados.teste.final)){
dados.teste.final[i,]$nNA = sum(is.na(dados.teste.final[i,2:7]))
}
}
for(i in 1:nrow(dados.treino)){
for(j in 1:ncol(dados.treino)){
if(is.na(dados.treino[i,j])){
if(6-(sum(is.na(dados.treino[i,2:7]))) == 0){
dados.treino[i, j] = 0
}
else{
dados.treino[i, j] = sum(dados.treino[i,2:7], na.rm = T)/(6-sum(is.na(dados.treino[i,2:7])))
}
}
if(i <= nrow(dados.teste) && j <= ncol(dados.teste)){
if(is.na(dados.teste[i,j])){
if(6-sum(is.na(dados.teste[i,2:7])) == 0){
dados.teste[i, j] = 0
}
else{
dados.teste[i, j] = sum(dados.teste[i,2:7], na.rm = T)/(6-sum(is.na(dados.teste[i,2:7])))
}
}
}
if(i <= nrow(dados.teste.final) && j <= ncol(dados.teste.final)){
if(is.na(dados.teste.final[i,j])){
if(6-sum(is.na(dados.teste.final[i,2:7])) == 0){
dados.teste.final[i, j] = 0
}
else{
dados.teste.final[i, j] = sum(dados.teste.final[i,2:7], na.rm = T)/(6-sum(is.na(dados.teste.final[i,2:7])))
}
}
}
}
}
dados.treino$Programacao[dados.treino$Programação.I >= 5 & dados.treino$Laboratório.de.Programação.I >= 5 ] = 0
dados.teste$Programacao[dados.teste$Programação.I >= 5 & dados.teste$Laboratório.de.Programação.I >= 5 ] = 0
dados.teste.final$Programacao[dados.teste.final$Programação.I >= 5 & dados.teste.final$Laboratório.de.Programação.I >= 5 ] = 0
dados.treino$Programacao[is.na(dados.treino$Programacao)] = 1
dados.teste$Programacao[is.na(dados.teste$Programacao)] = 1
dados.teste.final$Programacao[is.na(dados.teste.final$Programacao)] = 1Criando modelo de árvore de decisão
dt.control = rpart.control(maxdepth=30)
model.tree = rpart(EVADIU ~ .,
data=select(dados.treino, -MAT_ALU_MATRICULA),
method="class",
control=dt.control)
best.tree = prune(model.tree,
+ model.tree$cptable[which.min(model.tree$cptable[,"xerror"]),"CP"])Acurácia, precision e recall
Criamos o novo modelo de árvore de decisão e pudemos notar algumas melhoras. Apesar de o valor de Recall ter permanecido o mesmo, a Acurácia foi minimamente elevada e a Precisão subiu em quase 0.20 “pontos”.
predicao = as.data.frame(predict(model.tree, select(dados.teste, -MAT_ALU_MATRICULA)))
temp = apply(predicao['TRUE'], 2, FUN = function(x){return(x > 0.5)})
dados.teste$predicao = as.factor(temp)
temp = dados.teste %>% select(EVADIU, predicao)
TP = subset(temp, EVADIU == TRUE & predicao == TRUE) %>% nrow()
TN = subset(temp, EVADIU == FALSE & predicao == FALSE) %>% nrow()
FP = subset(temp, EVADIU == FALSE & predicao == TRUE) %>% nrow()
FN = subset(temp, EVADIU == TRUE & predicao == FALSE) %>% nrow()Conferindo os valores abaixo é possível conrfimar que o novo modelo apresenta melhores resultados para o teste executado. Sendo assim, esse será o modelo utilizado para tentar prever quais alunos irão evadir no desafio do Kaggle.
print(paste('Acurácia:', (TP + TN)/(TP+TN+FP+FN)))## [1] "Acurácia: 0.945652173913043"print(paste('Precisão:', TP / (TP + FP)))## [1] "Precisão: 0.625"print(paste('Recall:', TP / (TP + FN)))## [1] "Recall: 0.714285714285714"Para mais detalhes, observe o a informações da matriz de confusão abaixo:
confusionMatrix(dados.teste$EVADIU, dados.teste$predicao)## Confusion Matrix and Statistics
##
## Reference
## Prediction FALSE TRUE
## FALSE 82 3
## TRUE 2 5
##
## Accuracy : 0.9457
## 95% CI : (0.8777, 0.9821)
## No Information Rate : 0.913
## P-Value [Acc > NIR] : 0.1789
##
## Kappa : 0.6372
## Mcnemar's Test P-Value : 1.0000
##
## Sensitivity : 0.9762
## Specificity : 0.6250
## Pos Pred Value : 0.9647
## Neg Pred Value : 0.7143
## Prevalence : 0.9130
## Detection Rate : 0.8913
## Detection Prevalence : 0.9239
## Balanced Accuracy : 0.8006
##
## 'Positive' Class : FALSE
## Utilizando o melhor modelo no desafio Kaggle
predicao.final = as.data.frame(predict(best.tree, select(dados.teste.final, -MAT_ALU_MATRICULA), type = "class"))
result = select(dados.teste.final, MAT_ALU_MATRICULA) %>% cbind(predicao.final)
names(result) = c("MAT_ALU_MATRICULA", "EVADIU")
write.csv(result, "resultado.csv", row.names = F)