Durante os últimos anos a Universidade Federal de Campina Grande observa um número alto de evasão por parte dos alunos. Tentando entender os motivos dessa evasão analisamos uma amostra contendo dados importante. A nossa amostra tem os seguintes atributos:
O nosso objetivo é construir um modelo de classificação que nos diga se o aluno irá evadir ou não. Vamos classificar apenas para os alunos que tem período relativo 5.
library(plyr)
library(dplyr)
arquivo <- read.csv("~/Projetos/DataAnalysis/Assignment5/training_evasao_sem_acento.csv")
#Transformação para factor
arquivo$COD_EVASAO <- as.factor(arquivo$COD_EVASAO)
arquivo$COD_CURSO <- as.factor(arquivo$COD_CURSO)
arquivo$CODIGO <- as.factor(arquivo$CODIGO)
arquivo$CREDITOS <- as.factor(arquivo$CREDITOS)
arquivo$MEDIA[is.na(arquivo$MEDIA)] <- -1
Antes de criar o modelo é importante dividir o arquivo original em treino e teste (75% treinamento, 25% teste), para assim verificar o F-measure e saber se um modelo criado é melhor do que o modelo anterior.
#Primeiro periodo
set.seed(12345)
arquivo <- filter(arquivo, PERIODO_RELATIVO == 5)
arquivo <- arquivo[order(runif(nrow(arquivo))), ]
#Divisao de treino e teste
treino <- arquivo[1:round(0.75*nrow(arquivo)), ]
test <- arquivo[round(0.75*nrow(arquivo)):nrow(arquivo), ]
Podemos notar que a proporção entre evasão e não evasão se manteve parecida após a divisão de treino e teste.
prop.table(table(arquivo$COD_EVASAO))
##
## 0 1
## 0.94101331 0.05898669
prop.table(table(treino$COD_EVASAO))
##
## 0 1
## 0.94206114 0.05793886
prop.table(table(test$COD_EVASAO))
##
## 0 1
## 0.93791574 0.06208426
Devemos agora decidir qual classificador iremos utilizar, SVM, kNN, árvores/florestas aleatórias. Para ajudar na nossa escolha utilizamos a biblioteca caret. Foram utilizados os mesmo atributos do que foi entregue no problema 5. (período letivo da universidade, código da disciplina cursada, departamento e a situação)
library(caret)
## Loading required package: lattice
## Loading required package: ggplot2
library(e1071)
library("C50")
treino_labels = treino[, 14] ## Classes das instâncias de treino
test_labels = test[, 14] ## Classes das instâncias de teste
treino = treino[-14] ## Exclui variável alvo
#Transformação para numeric
treino$PERIODO <- as.numeric(treino$PERIODO)
treino$DISCIPLINA <- as.numeric(treino$DISCIPLINA)
treino$DEPARTAMENTO <- as.numeric(treino$DEPARTAMENTO)
treino$SITUACAO <- as.numeric(treino$SITUACAO)
treino$MEDIA <- as.numeric(treino$MEDIA)
best_tree_model = train(treino[c(5,7,9,11)], treino_labels, method="C5.0", preProcess=c("range"))
## Warning in predict.C5.0(modelFit, newdata, trial = submodels$trials[j]):
## 'trials' should be <= 1 for this object. Predictions generated using 1
## trials
## Warning in predict.C5.0(modelFit, newdata, trial = submodels$trials[j]):
## 'trials' should be <= 1 for this object. Predictions generated using 1
## trials
## Warning in predict.C5.0(modelFit, newdata, trial = submodels$trials[j]):
## 'trials' should be <= 1 for this object. Predictions generated using 1
## trials
## Warning in predict.C5.0(modelFit, newdata, trial = submodels$trials[j]):
## 'trials' should be <= 1 for this object. Predictions generated using 1
## trials
accuracy_tree = max(best_tree_model$resample$Accuracy)
best_tree_model
## C5.0
##
## 4056 samples
## 4 predictors
## 2 classes: '0', '1'
##
## Pre-processing: re-scaling to [0, 1]
## Resampling: Bootstrapped (25 reps)
##
## Summary of sample sizes: 4056, 4056, 4056, 4056, 4056, 4056, ...
##
## Resampling results across tuning parameters:
##
## model winnow trials Accuracy Kappa Accuracy SD Kappa SD
## rules FALSE 1 0.9426848 0.2131663 0.004937709 0.05349996
## rules FALSE 10 0.9433501 0.2185739 0.005511165 0.06101470
## rules FALSE 20 0.9439582 0.2158950 0.004597965 0.05134013
## rules TRUE 1 0.9426848 0.2131663 0.004937709 0.05349996
## rules TRUE 10 0.9433501 0.2185739 0.005511165 0.06101470
## rules TRUE 20 0.9439582 0.2158950 0.004597965 0.05134013
## tree FALSE 1 0.9419256 0.2612173 0.005103789 0.05303757
## tree FALSE 10 0.9426087 0.2110566 0.005745310 0.05915326
## tree FALSE 20 0.9437203 0.2268574 0.005323514 0.06196789
## tree TRUE 1 0.9419256 0.2612173 0.005103789 0.05303757
## tree TRUE 10 0.9426087 0.2110566 0.005745310 0.05915326
## tree TRUE 20 0.9437203 0.2268574 0.005323514 0.06196789
##
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were trials = 20, model = rules
## and winnow = TRUE.
Podemos observar que utilizando árvore/floresta o caret encontrou a melhor solução como sendo utilizando trials = 20, model = rules and winnow = TRUE.
Vamos agora utilizar o caret para encontrar a melhor solução utilizando o kNN como classificador.
best_knn_model <- train(treino[c(5,7,9,11)], treino_labels,
method = "knn",
preProcess = c("range"))
accuracy_knn = max(best_knn_model$resample$Accuracy)
best_knn_model
## k-Nearest Neighbors
##
## 4056 samples
## 4 predictors
## 2 classes: '0', '1'
##
## Pre-processing: re-scaling to [0, 1]
## Resampling: Bootstrapped (25 reps)
##
## Summary of sample sizes: 4056, 4056, 4056, 4056, 4056, 4056, ...
##
## Resampling results across tuning parameters:
##
## k Accuracy Kappa Accuracy SD Kappa SD
## 5 0.9295698 0.1988719 0.006757194 0.05493821
## 7 0.9351671 0.2092614 0.006571791 0.06096505
## 9 0.9377185 0.2136137 0.006436711 0.05163939
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was k = 9.
Podemos observar que utilizando kNN o caret encontrou a melhor solução como sendo utilizando k = 9.
#Vamos agora utilizar o caret para encontrar a melhor solução para o SVM como classificardor.
#library("kernlab")
#best_svm_model <- train(treino[c(5,7,9,11)], treino_labels,
# method = "svmRadial",
# preProcess = c("range"))
#accuracy_svn = max(best_svm_model$resample$Accuracy)
#best_svm_model
Agora para ajudar na escolha do melhor classificador para esse problema vamos observar a acurácia dos dois classificadores:
accuracy_tree
## [1] 0.9515449
accuracy_knn
## [1] 0.9497061
Podemos observar que a floresta obteve valor mais alto. Por esse motivo escolhemos esse classificador com os parâmetros trials = 20, model = rules and winnow = TRUE para realizar a submissão no kaggle.
Agora que já temos o nosso modelo ideial vamos criar novos atributos para melhorar o nosso classificador:
#Criando novos atributos para o treino
treino_group <- group_by(treino, MATRICULA)
media <- summarise(treino_group, mean(MEDIA))
names(media) <- c("MATRICULA", "MEDIATOTAL")
reprovacao <- summarise(group_by(filter(treino, SITUACAO=="Reprovado"), MATRICULA), n())
names(reprovacao) <- c("MATRICULA", "REPROVACAO")
aprovado <- summarise(group_by(filter(treino, SITUACAO=="Aprovado"), MATRICULA), n())
names(aprovado) <- c("MATRICULA", "APROVADO")
target <- c("Reprovado por Falta", NA)
reprovado_falta <- summarise(group_by(filter(treino, SITUACAO %in% target), MATRICULA), n())
names(reprovado_falta) <- c("MATRICULA", "REPROVADOFALTA")
target <- c("Reprovado por Falta", "Reprovado", "Trancado")
n_aprovado <- summarise(group_by(filter(treino, SITUACAO %in% target), MATRICULA), n())
names(n_aprovado) <- c("MATRICULA", "NAPROVADO")
#Merge dos novos atributos
treino <- merge(treino, media, by = "MATRICULA", all = TRUE)
treino <- merge(treino, reprovacao, by = "MATRICULA", all = TRUE)
treino <- merge(treino, aprovado, by = "MATRICULA", all = TRUE)
treino <- merge(treino, reprovado_falta, by = "MATRICULA", all = TRUE)
treino <- merge(treino, n_aprovado, by = "MATRICULA", all = TRUE)
#Transformando NA em 0
treino[is.na(treino)] <- 0
#Criando novos atributos para o teste
test_group <- group_by(test, MATRICULA)
media <- summarise(test_group, mean(MEDIA))
names(media) <- c("MATRICULA", "MEDIATOTAL")
reprovacao <- summarise(group_by(filter(test, SITUACAO=="Reprovado"), MATRICULA), n())
names(reprovacao) <- c("MATRICULA", "REPROVACAO")
aprovado <- summarise(group_by(filter(test, SITUACAO=="Aprovado"), MATRICULA), n())
names(aprovado) <- c("MATRICULA", "APROVADO")
target <- c("Reprovado por Falta", NA)
reprovado_falta <- summarise(group_by(filter(test, SITUACAO %in% target), MATRICULA), n())
names(reprovado_falta) <- c("MATRICULA", "REPROVADOFALTA")
target <- c("Reprovado por Falta", "Reprovado", "Trancado")
n_aprovado <- summarise(group_by(filter(test, SITUACAO %in% target), MATRICULA), n())
names(n_aprovado) <- c("MATRICULA", "NAPROVADO")
test <- merge(test, media, by = "MATRICULA", all = TRUE)
test <- merge(test, reprovacao, by = "MATRICULA", all = TRUE)
test <- merge(test, aprovado, by = "MATRICULA", all = TRUE)
test <- merge(test, reprovado_falta, by = "MATRICULA", all = TRUE)
test <- merge(test, n_aprovado, by = "MATRICULA", all = TRUE)
#Transformação para numeric
test$PERIODO <- as.numeric(test$PERIODO)
test$DISCIPLINA <- as.numeric(test$DISCIPLINA)
test$DEPARTAMENTO <- as.numeric(test$DEPARTAMENTO)
test$SITUACAO <- as.numeric(test$SITUACAO)
test$MEDIA <- as.numeric(test$MEDIA)
test[is.na(test)] <- 0
Agora com as 4 novas colunas criadas temos mais dados para analisar e ajudar no classificador.
model <- C5.0(treino[,c(3,5,7,9,10,11,14,15,16,17,18)], treino_labels, trials = 20, model = rules, winnow = TRUE)
model
##
## Call:
## C5.0.default(x = treino[, c(3, 5, 7, 9, 10, 11, 14, 15, 16, 17, 18)], y
## = treino_labels, trials = 20, model = rules, winnow = TRUE)
##
## Classification Tree
## Number of samples: 4056
## Number of predictors: 11
##
## Number of boosting iterations: 20 requested; 1 used due to early stopping
##
## Non-standard options: attempt to group attributes
summary(model)
##
## Call:
## C5.0.default(x = treino[, c(3, 5, 7, 9, 10, 11, 14, 15, 16, 17, 18)], y
## = treino_labels, trials = 20, model = rules, winnow = TRUE)
##
##
## C5.0 [Release 2.07 GPL Edition] Mon Aug 24 11:15:46 2015
## -------------------------------
##
## Class specified by attribute `outcome'
##
## Read 4056 cases (12 attributes) from undefined.data
##
## ----- Trial 0: -----
##
## Decision tree:
## 0 (4056/235)
##
## ----- Trial 1: -----
##
## Decision tree:
## 0 (4056/1131.5)
##
## *** boosting reduced to 1 trial since last classifier is very inaccurate
##
## *** boosting abandoned (too few classifiers)
##
##
## Evaluation on training data (4056 cases):
##
## Decision Tree
## ----------------
## Size Errors
##
## 1 235( 5.8%) <<
##
##
## (a) (b) <-classified as
## ---- ----
## 3821 (a): class 0
## 235 (b): class 1
##
##
## Time: 0.1 secs
pred <- predict(model, test[,c(3,5,7,9,10,11,15,16,17,18,19)])
true_eva <- test$COD_EVASAO == 1
table(pred, true_eva)
## true_eva
## pred FALSE TRUE
## 0 1269 84
## 1 0 0
Podemos notar que não criamos um bom classificador, pois ele classificou todas as saidas como sendo 0. Temos esse erro pois estamos com dados desbalanceados. No nosso arquivo de treino 94% dos dados são de não evasão e 6% dos dados são evasão.
Por essa razão resolvemos por, nos dados de treino, balancear os dados.
treino <- cbind(treino, treino_labels)
treino_positivo = filter(treino, treino_labels == 1)
treino_negativo = filter(treino, treino_labels == 0)
treino_negativo <- treino_negativo[order(runif(nrow(treino_positivo))), ]
novo_treino <- rbind(treino_positivo,treino_negativo)
novo_treino <- novo_treino[order(runif(nrow(novo_treino))), ]
prop.table(table(novo_treino$treino_labels))
##
## 0 1
## 0.5 0.5
Vamos criar um novo modelo agora com os dados balanceados:
model <- C5.0(novo_treino[,c(3,5,7,9,10,11,14,15,16,17,18)], novo_treino$treino_labels, trials = 20, model = rules, winnow = TRUE)
model
##
## Call:
## C5.0.default(x = novo_treino[, c(3, 5, 7, 9, 10, 11, 14, 15, 16, 17,
## 18)], y = novo_treino$treino_labels, trials = 20, model = rules, winnow
## = TRUE)
##
## Classification Tree
## Number of samples: 470
## Number of predictors: 11
##
## Number of boosting iterations: 20 requested; 1 used due to early stopping
##
## Non-standard options: attempt to group attributes
summary(model)
##
## Call:
## C5.0.default(x = novo_treino[, c(3, 5, 7, 9, 10, 11, 14, 15, 16, 17,
## 18)], y = novo_treino$treino_labels, trials = 20, model = rules, winnow
## = TRUE)
##
##
## C5.0 [Release 2.07 GPL Edition] Mon Aug 24 11:15:48 2015
## -------------------------------
##
## Class specified by attribute `outcome'
##
## Read 470 cases (12 attributes) from undefined.data
##
## ----- Trial 0: -----
##
## Decision tree:
##
## SITUACAO > 3: 0 (30/6)
## SITUACAO <= 3:
## :...SITUACAO > 2: 1 (48/13)
## SITUACAO <= 2:
## :...DEPARTAMENTO > 11: 0 (163/70)
## DEPARTAMENTO <= 11:
## :...COD_CURSO = 14123100:
## :...PERIODO <= 2012.1: 1 (96/32)
## : PERIODO > 2012.1:
## : :...DEPARTAMENTO <= 7: 1 (5/1)
## : DEPARTAMENTO > 7: 0 (53/21)
## COD_CURSO = 12204100:
## :...PERIODO <= 2011.2: 0 (28/8)
## PERIODO > 2011.2:
## :...PERIODO <= 2012.1: 1 (8)
## PERIODO > 2012.1:
## :...MEDIATOTAL > 8.716666: 1 (4)
## MEDIATOTAL <= 8.716666:
## :...MEDIATOTAL <= 6.475: 1 (3)
## MEDIATOTAL > 6.475: 0 (32/12)
##
## ----- Trial 1: -----
##
## Decision tree:
##
## SITUACAO > 3: 0 (28.5/7.3)
## SITUACAO <= 3:
## :...SITUACAO > 2: 1 (46.8/15.9)
## SITUACAO <= 2:
## :...COD_CURSO = 12204100:
## :...PERIODO <= 2011.2: 0 (27.4/9.8)
## : PERIODO > 2011.2: 1 (45.5/17.7)
## COD_CURSO = 14123100:
## :...MEDIATOTAL > 6.8: 1 (95.7/31.7)
## MEDIATOTAL <= 6.8:
## :...SITUACAO <= 1: 0 (152.9/69.3)
## SITUACAO > 1:
## :...DEPARTAMENTO > 15: 1 (4.9)
## DEPARTAMENTO <= 15:
## :...DISCIPLINA <= 26: 0 (23.3/6.3)
## DISCIPLINA > 26: 1 (44.9/18.3)
##
## ----- Trial 2: -----
##
## Decision tree:
## 1 (470/224)
##
## *** boosting reduced to 2 trials since last classifier is very inaccurate
##
## *** boosting abandoned (too few classifiers)
##
##
## Evaluation on training data (470 cases):
##
## Decision Tree
## ----------------
## Size Errors
##
## 11 163(34.7%) <<
##
##
## (a) (b) <-classified as
## ---- ----
## 189 46 (a): class 0
## 117 118 (b): class 1
##
##
## Attribute usage:
##
## 100.00% SITUACAO
## 83.40% DEPARTAMENTO
## 48.72% COD_CURSO
## 48.72% PERIODO
## 8.30% MEDIATOTAL
##
##
## Time: 0.1 secs
pred <- predict(model, test[,c(3,5,7,9,10,11,15,16,17,18,19)])
true_eva <- test$COD_EVASAO == 1
table(pred, true_eva)
## true_eva
## pred FALSE TRUE
## 0 723 43
## 1 546 41
Temos agora um f-measure com o valor de:
precision <- 41/41+43
recall <- 41/(41+546)
fmeasure2 <- 2*precision*recall/(precision+recall)
fmeasure2
## [1] 0.139472
Se mostrando melhor do que o modelo anterior, porém com valor de f-measure ainda muito baixo.