A Universidade Federal de Campina Grande (UFCG), assim como muitas universidades brasileiras, sofre com o problema da evasão de alunos. Tendo como motivação o problema que deixa um número relevante de vagas ociosas no ensino público, neste relatório será feito um modelo de predição na intenção de descobrir, antecipadamente, quando um aluno irá ou não abandonar o seu curso, considerando que ele está cursando o primeiro período. Ao final da análise, será gerado um arquido com os dados da predição do modelo promovido, que será submetido em uma competição no Kaggle disponível neste link.

Os dados utilizados fazem parte de uma amostra disciplinas cursadas por alunos da Universidade Federal de Campina Grande, dos cursos de Engenharia Elétrica e Enfermagem, e a predição será feita através de um modelo construído a partir dos dados fornecidos na amostra.

Segue abaixo um sumário da amostra.

library(dplyr, quietly = TRUE)
library(ggplot2, quietly = TRUE)
library(caret)
library(C50)
training_evasao <- read.csv(file = "training_evasao.csv", header=TRUE, sep=",")
summary(training_evasao)
##        ID          MATRICULA           COD_CURSO       
##  Min.   :    1   Min.   :  2636462   Min.   :12204100  
##  1st Qu.: 4739   1st Qu.:249727234   1st Qu.:14123100  
##  Median : 9478   Median :508993893   Median :14123100  
##  Mean   : 9581   Mean   :501522998   Mean   :13677419  
##  3rd Qu.:14216   3rd Qu.:745324313   3rd Qu.:14123100  
##  Max.   :19536   Max.   :999280527   Max.   :14123100  
##                                                        
##                  CURSO          PERIODO         CODIGO       
##  ENFERMAGEM - D     : 4402   Min.   :2002   Min.   :1105013  
##  ENGENHARIA ELÉTRICA:14552   1st Qu.:2009   1st Qu.:1109103  
##                              Median :2011   Median :1201136  
##                              Mean   :2010   Mean   :1246218  
##                              3rd Qu.:2012   3rd Qu.:1404139  
##                              Max.   :2013   Max.   :1503072  
##                                                              
##                                   DISCIPLINA       CREDITOS    
##  INTRODUCAO A PROGRAMACAO              : 1436   Min.   :0.000  
##  INTRODUCAO A ENGENHARIA ELETRICA      : 1392   1st Qu.:3.000  
##  CIÊNCIAS DO AMBIENTE                  : 1377   Median :4.000  
##  EXPRESSAO GRAFICA                     : 1355   Mean   :3.536  
##  ÁLGEBRA VETORIAL E GEOMETRIA ANALÍTICA: 1341   3rd Qu.:4.000  
##  CALCULO DIFERENCIAL E INTEGRAL I      : 1334   Max.   :8.000  
##  (Other)                               :10719                  
##                                   DEPARTAMENTO      MEDIA       
##  UNID. ACAD. DE CIÊNCIAS DA SAÚDE (UACS):4402   Min.   : 0.000  
##  UNID. ACAD. DE ENGENHARIA ELÉTRICA     :3494   1st Qu.: 4.000  
##  UNID. ACAD. DE MATEMÁTICA              :3374   Median : 7.000  
##  UNID. ACAD. DE FÍSICA                  :2102   Mean   : 5.904  
##  UNID. ACAD. DE SISTEMAS E COMPUTAÇÃO   :1695   3rd Qu.: 8.200  
##  UNID. ACAD. DE ENGENHARIA CIVIL        :1420   Max.   :10.000  
##  (Other)                                :2467   NA's   :529     
##                 SITUACAO     PERIODO_INGRESSO PERIODO_RELATIVO
##  Aprovado           :13438   Min.   :2002     Min.   :1.000   
##  Reprovado          : 2906   1st Qu.:2008     1st Qu.:1.000   
##  Reprovado por Falta: 2049   Median :2010     Median :1.000   
##  Trancado           :  561   Mean   :2009     Mean   :2.141   
##                              3rd Qu.:2011     3rd Qu.:5.000   
##                              Max.   :2013     Max.   :5.000   
##                                                               
##    COD_EVASAO     
##  Min.   :0.00000  
##  1st Qu.:0.00000  
##  Median :0.00000  
##  Mean   :0.09291  
##  3rd Qu.:0.00000  
##  Max.   :1.00000  
## 

Seleção das Variáveis

Para o processo de predição, foram consideradas relevantes as variáveis “CURSO”, “MEDIA” e “SITUACAO”, devido a sua importância para a identificação dos perfis dos universitários.

alunos_fera <- training_evasao %>% filter(PERIODO_RELATIVO ==  1)
alunos_fera <- alunos_fera %>% select(ID,MATRICULA,CURSO, MEDIA, SITUACAO, COD_EVASAO)

alunos_fera$COD_EVASAO <- as.factor(alunos_fera$COD_EVASAO)
alunos_fera$MATRICULA <- as.factor(alunos_fera$MATRICULA)
str(alunos_fera)
## 'data.frame':    13546 obs. of  6 variables:
##  $ ID        : int  2 4 5 6 11 13 15 16 17 18 ...
##  $ MATRICULA : Factor w/ 1871 levels "2636462","2714857",..: 1386 1386 1386 1386 1386 1386 1386 35 35 35 ...
##  $ CURSO     : Factor w/ 2 levels "ENFERMAGEM - D",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ MEDIA     : num  8.3 9.5 9.8 8.2 7.9 8.6 8 9.3 9.1 7.7 ...
##  $ SITUACAO  : Factor w/ 4 levels "Aprovado","Reprovado",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ COD_EVASAO: Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...

Sumarização dos Dados

Além das variáveis escolhidas, também é necessária a utilização de outras informações, ainda não disponibilizadas diretamente pelo dataset, para o treinamento do modelo de predição. Para tanto, foram geradas novas variáveis através do processo de sumarização. Segue abaixo a listagem e descrição de cada variável criada e o codigo utilizado.

df <- alunos_fera %>% group_by(MATRICULA, CURSO, COD_EVASAO) %>% summarise(
    MEDIA = round(sum(MEDIA, na.rm = TRUE)/(n() -(ifelse(NA %in% MEDIA, count_NAs(MEDIA) ,0))),digits=2),
    TEM_APROVACAO=ifelse("Aprovado" %in% SITUACAO, TRUE, FALSE),
    TEM_REPROV_POR_NOTA=ifelse("Reprovado" %in% SITUACAO, TRUE, FALSE),
    TEM_REPROV_POR_FALTA=ifelse("Reprovado por Falta" %in% SITUACAO,TRUE,FALSE),
    MEDIA_ZERO=ifelse(MEDIA == 0, TRUE, FALSE),
    REP_POR_FALTA_E_MEDIA_ZERO = ( TEM_REPROV_POR_FALTA && MEDIA_ZERO))

summary(df)
##    MATRICULA                    CURSO      COD_EVASAO     MEDIA      
##  2636462:   1   ENFERMAGEM - D     : 384   0:1668     Min.   :0.000  
##  2714857:   1   ENGENHARIA ELÉTRICA:1487   1: 203     1st Qu.:4.662  
##  2791762:   1                                         Median :6.485  
##  3521919:   1                                         Mean   :5.815  
##  5062803:   1                                         3rd Qu.:7.610  
##  5604386:   1                                         Max.   :9.600  
##  (Other):1865                                         NA's   :37     
##  TEM_APROVACAO   TEM_REPROV_POR_NOTA TEM_REPROV_POR_FALTA MEDIA_ZERO     
##  Mode :logical   Mode :logical       Mode :logical        Mode :logical  
##  FALSE:194       FALSE:937           FALSE:1439           FALSE:1723     
##  TRUE :1677      TRUE :934           TRUE :432            TRUE :111      
##  NA's :0         NA's :0             NA's :0              NA's :37       
##                                                                          
##                                                                          
##                                                                          
##  REP_POR_FALTA_E_MEDIA_ZERO
##  Mode :logical             
##  FALSE:1762                
##  TRUE :109                 
##  NA's :0                   
##                            
##                            
## 
df <- df[c(2,4,5,6,7,8,9,3)] #Reordenando as colunas
df$TEM_APROVACAO <- as.factor(df$TEM_APROVACAO)
df$TEM_REPROV_POR_NOTA <- as.factor(df$TEM_REPROV_POR_NOTA)
df$TEM_REPROV_POR_FALTA <- as.factor(df$TEM_REPROV_POR_FALTA)
df$MEDIA_ZERO <- as.factor(df$MEDIA_ZERO)
df$REP_POR_FALTA_E_MEDIA_ZERO <- as.factor(df$REP_POR_FALTA_E_MEDIA_ZERO)
df <- data.frame(df)
str(df)
## 'data.frame':    1871 obs. of  8 variables:
##  $ CURSO                     : Factor w/ 2 levels "ENFERMAGEM - D",..: 1 2 2 1 2 2 2 1 2 1 ...
##  $ MEDIA                     : num  0 7.84 5.37 7 7.87 8.23 3.35 7.34 7.73 7.78 ...
##  $ TEM_APROVACAO             : Factor w/ 2 levels "FALSE","TRUE": 1 2 2 2 2 2 2 2 2 2 ...
##  $ TEM_REPROV_POR_NOTA       : Factor w/ 2 levels "FALSE","TRUE": 1 2 2 1 1 1 2 2 1 1 ...
##  $ TEM_REPROV_POR_FALTA      : Factor w/ 2 levels "FALSE","TRUE": 2 1 1 1 1 1 2 1 1 1 ...
##  $ MEDIA_ZERO                : Factor w/ 2 levels "FALSE","TRUE": 2 1 1 1 1 1 1 1 1 1 ...
##  $ REP_POR_FALTA_E_MEDIA_ZERO: Factor w/ 2 levels "FALSE","TRUE": 2 1 1 1 1 1 1 1 1 1 ...
##  $ COD_EVASAO                : Factor w/ 2 levels "0","1": 2 1 1 1 1 1 1 1 1 1 ...

Particionando os Dados

Agora que temos um dataframe com todas as variáveis consideradas interessantes para analisar o modelo, dividiremos o conjunto de dados em conjuntos de treino e teste. Será feito, então, um treinamento do modelo com uma parte dos dados da amostra e, em seguida, este será testado com a outra parte da amostra, afim de verificar o nível de acerto das suas predições.

trainIndex <- createDataPartition(df$COD_EVASAO, p = .9, list = FALSE, times = 1)

dfTrain <- df[trainIndex,]
dfTest <- df[-trainIndex,]

O modelo será treinado através da técnica de floresta de árvores aleatórias.

modelo_de_teste <- C5.0(dfTrain[-8],dfTrain$COD_EVASAO, trials = 10)
modelo_de_teste
## 
## Call:
## C5.0.default(x = dfTrain[-8], y = dfTrain$COD_EVASAO, trials = 10)
## 
## Classification Tree
## Number of samples: 1685 
## Number of predictors: 7 
## 
## Number of boosting iterations: 10 requested;  6 used due to early stopping
## Average tree size: 2.2 
## 
## Non-standard options: attempt to group attributes
summary(modelo_de_teste)
## 
## Call:
## C5.0.default(x = dfTrain[-8], y = dfTrain$COD_EVASAO, trials = 10)
## 
## 
## C5.0 [Release 2.07 GPL Edition]      Wed Jul 29 19:21:32 2015
## -------------------------------
## 
## Class specified by attribute `outcome'
## 
## Read 1685 cases (8 attributes) from undefined.data
## 
## -----  Trial 0:  -----
## 
## Decision tree:
## 
## TEM_APROVACAO = FALSE: 1 (172/40)
## TEM_APROVACAO = TRUE: 0 (1513/51)
## 
## -----  Trial 1:  -----
## 
## Decision tree:
## 
## REP_POR_FALTA_E_MEDIA_ZERO = FALSE: 0 (1579.5/292.9)
## REP_POR_FALTA_E_MEDIA_ZERO = TRUE: 1 (105.5/35.9)
## 
## -----  Trial 2:  -----
## 
## Decision tree:
## 
## MEDIA <= 4.67: 1 (689.5/303.2)
## MEDIA > 4.67: 0 (995.5/201.7)
## 
## -----  Trial 3:  -----
## 
## Decision tree:
## 
## MEDIA <= 3.17: 1 (491.2/227.5)
## MEDIA > 3.17: 0 (1193.8/292.6)
## 
## -----  Trial 4:  -----
## 
## Decision tree:
## 
## TEM_APROVACAO = FALSE: 0 (397.7/185)
## TEM_APROVACAO = TRUE:
## :...MEDIA <= 1.99: 1 (102.7/40.1)
##     MEDIA > 1.99: 0 (1184.6/331.6)
## 
## -----  Trial 5:  -----
## 
## Decision tree:
## 
## TEM_APROVACAO = FALSE: 1 (470.5/185.8)
## TEM_APROVACAO = TRUE: 0 (1214.5/416.5)
## 
## -----  Trial 6:  -----
## 
## Decision tree:
##  0 (1663/574.2)
## 
## *** boosting reduced to 6 trials since last classifier is very inaccurate
## 
## 
## Evaluation on training data (1685 cases):
## 
## Trial        Decision Tree   
## -----      ----------------  
##    Size      Errors  
## 
##    0      2   62( 3.7%)
##    1      2   69( 4.1%)
##    2      2  290(17.2%)
##    3      2  106( 6.3%)
##    4      3  159( 9.4%)
##    5      2   62( 3.7%)
## boost             60( 3.6%)   <<
## 
## 
##     (a)   (b)    <-classified as
##    ----  ----
##    1507    24    (a): class 0
##      36   118    (b): class 1
## 
## 
##  Attribute usage:
## 
##  100.00% TEM_APROVACAO
##  100.00% REP_POR_FALTA_E_MEDIA_ZERO
##   97.98% MEDIA
## 
## 
## Time: 0.0 secs

Teste de Predição

Façamos agora o teste de predição através da função predict. Seguem abaixo os resultados obtidos.

teste_predicao <- predict(modelo_de_teste, dfTest)
summary(teste_predicao)
##   0   1 
## 168  18
summary(dfTest$COD_EVASAO)
##   0   1 
## 166  20

Percentual de acerto:

num_acertos <- sum(teste_predicao == dfTest$COD_EVASAO)
num_acertos/length(dfTest$COD_EVASAO)
## [1] 0.9139785

Cálculo do F-Measure

Para avaliar a precisão da predição, será utilizado como métrica o F-mesure, que consiste em uma média ponderada de precisao e recall. Seguem abaixo a matriz de confusão utilizada para o cálculo e o F-measure.

df_resp <- data.frame(teste_predicao, COD_EVASAO=dfTest$COD_EVASAO, row.names=NULL)
matriz_de_confusao <- table(df_resp)
matriz_de_confusao
##               COD_EVASAO
## teste_predicao   0   1
##              0 159   9
##              1   7  11
#A matriz eh usada pra calcular o F-Measure. F-measure eh a metrica que ele pede no checkpoint pra calcular.
df_resp$teste_predicao <- as.numeric(df_resp$teste_predicao) - 1
df_resp$COD_EVASAO <- as.numeric(df_resp$COD_EVASAO) - 1
str(df_resp)
## 'data.frame':    186 obs. of  2 variables:
##  $ teste_predicao: num  0 0 0 0 0 0 0 0 1 1 ...
##  $ COD_EVASAO    : num  0 0 0 0 0 0 0 0 0 1 ...
summary(df_resp)
##  teste_predicao      COD_EVASAO    
##  Min.   :0.00000   Min.   :0.0000  
##  1st Qu.:0.00000   1st Qu.:0.0000  
##  Median :0.00000   Median :0.0000  
##  Mean   :0.09677   Mean   :0.1075  
##  3rd Qu.:0.00000   3rd Qu.:0.0000  
##  Max.   :1.00000   Max.   :1.0000
retrieved <- sum(df_resp$teste_predicao)

precision <- matriz_de_confusao[4]/(matriz_de_confusao[4] + matriz_de_confusao[2])
recall <- matriz_de_confusao[4] / (matriz_de_confusao[4] + matriz_de_confusao[3])
F_measure <- 2 * (precision * recall) / (precision + recall)
F_measure
## [1] 0.5789474

Modelo Final

Agora, os dados que anteriormente foram divididos em grupos de treino e teste serão utilizados inteiramente para teste e será feita a predição em um segundo dataset. Desta forma, será criado um nomo modelo, que será utilizado para os dados deste novo dataset.

modelo_de_submissao <- C5.0(df[-8],df$COD_EVASAO, trials = 10)
modelo_de_submissao
## 
## Call:
## C5.0.default(x = df[-8], y = df$COD_EVASAO, trials = 10)
## 
## Classification Tree
## Number of samples: 1871 
## Number of predictors: 7 
## 
## Number of boosting iterations: 10 requested;  3 used due to early stopping
## Average tree size: 2 
## 
## Non-standard options: attempt to group attributes
summary(modelo_de_submissao)
## 
## Call:
## C5.0.default(x = df[-8], y = df$COD_EVASAO, trials = 10)
## 
## 
## C5.0 [Release 2.07 GPL Edition]      Wed Jul 29 19:21:32 2015
## -------------------------------
## 
## Class specified by attribute `outcome'
## 
## Read 1871 cases (8 attributes) from undefined.data
## 
## -----  Trial 0:  -----
## 
## Decision tree:
## 
## TEM_APROVACAO = FALSE: 1 (194/48)
## TEM_APROVACAO = TRUE: 0 (1677/57)
## 
## -----  Trial 1:  -----
## 
## Decision tree:
## 
## REP_POR_FALTA_E_MEDIA_ZERO = FALSE: 0 (1741.5/319.1)
## REP_POR_FALTA_E_MEDIA_ZERO = TRUE: 1 (129.5/54.5)
## 
## -----  Trial 2:  -----
## 
## Decision tree:
## 
## MEDIA <= 3.17: 1 (557.6/224)
## MEDIA > 3.17: 0 (1313.4/300.7)
## 
## -----  Trial 3:  -----
## 
## Decision tree:
##  0 (1871/657.6)
## 
## *** boosting reduced to 3 trials since last classifier is very inaccurate
## 
## 
## Evaluation on training data (1871 cases):
## 
## Trial        Decision Tree   
## -----      ----------------  
##    Size      Errors  
## 
##    0      2  105( 5.6%)
##    1      2  116( 6.2%)
##    2      2  156( 8.3%)
## boost            106( 5.7%)   <<
## 
## 
##     (a)   (b)    <-classified as
##    ----  ----
##    1638    30    (a): class 0
##      76   127    (b): class 1
## 
## 
##  Attribute usage:
## 
##  100.00% TEM_APROVACAO
##  100.00% REP_POR_FALTA_E_MEDIA_ZERO
##   98.02% MEDIA
## 
## 
## Time: 0.0 secs

No código abaixo, é feita a sumarização novamente, desta vez para os novos dados de teste necessários para realizar a prediçao.

test_first_round_kaggle <- read.csv(file = "test_first_round_kaggle.csv", header=TRUE, sep=",")

df_teste <- test_first_round_kaggle %>% group_by(MATRICULA, CURSO) %>% summarise(
    MEDIA = round(sum(MEDIA, na.rm = TRUE)/(n() -(ifelse(NA %in% MEDIA, count_NAs(MEDIA) ,0))),digits=2),
    TEM_APROVACAO=ifelse("Aprovado" %in% SITUACAO, TRUE, FALSE),
    TEM_REPROV_POR_NOTA=ifelse("Reprovado" %in% SITUACAO, TRUE, FALSE),
    TEM_REPROV_POR_FALTA=ifelse("Reprovado por Falta" %in% SITUACAO,TRUE,FALSE),
    MEDIA_ZERO=ifelse(MEDIA == 0, TRUE, FALSE),
    REP_POR_FALTA_E_MEDIA_ZERO = ( TEM_REPROV_POR_FALTA && MEDIA_ZERO))


df_teste$TEM_APROVACAO <- as.factor(df_teste$TEM_APROVACAO)
df_teste$TEM_REPROV_POR_NOTA <- as.factor(df_teste$TEM_REPROV_POR_NOTA)
df_teste$TEM_REPROV_POR_FALTA <- as.factor(df_teste$TEM_REPROV_POR_FALTA)
df_teste$MEDIA_ZERO <- as.factor(df_teste$MEDIA_ZERO)
df_teste$REP_POR_FALTA_E_MEDIA_ZERO <- as.factor(df_teste$REP_POR_FALTA_E_MEDIA_ZERO)
df_teste <- data.frame(df_teste)
str(df_teste)
## 'data.frame':    123 obs. of  8 variables:
##  $ MATRICULA                 : int  1565650 8696570 9822368 11928230 35551592 38116320 38449794 51710560 58841481 59967279 ...
##  $ CURSO                     : Factor w/ 2 levels "ENFERMAGEM - D",..: 2 1 2 2 2 1 2 2 1 2 ...
##  $ MEDIA                     : num  3.36 8.34 5 7.81 4.37 1.73 9.21 9.41 7.64 6.29 ...
##  $ TEM_APROVACAO             : Factor w/ 2 levels "FALSE","TRUE": 2 2 2 2 2 2 2 2 2 2 ...
##  $ TEM_REPROV_POR_NOTA       : Factor w/ 2 levels "FALSE","TRUE": 2 1 2 1 2 1 1 1 1 2 ...
##  $ TEM_REPROV_POR_FALTA      : Factor w/ 2 levels "FALSE","TRUE": 1 1 1 1 1 2 1 1 1 1 ...
##  $ MEDIA_ZERO                : Factor w/ 2 levels "FALSE","TRUE": 1 1 1 1 1 1 1 1 1 1 ...
##  $ REP_POR_FALTA_E_MEDIA_ZERO: Factor w/ 2 levels "FALSE","TRUE": 1 1 1 1 1 1 1 1 1 1 ...

Em seguida, é feito o teste de predição através da função predict, como exposto abaixo.

predicao <- predict(modelo_de_submissao, df_teste)
summary(predicao)
##   0   1 
## 118   5

Por último, podemos gerar o arquivo que será submetido à competição aberta no Kaggle.

pred <- cbind(df_teste["MATRICULA"], COD_EVASAO=predicao)

ids <- test_first_round_kaggle[c("ID","MATRICULA")]

submissao <- left_join(x=ids, y=pred, by="MATRICULA")
submissao <- submissao[c("ID", "COD_EVASAO")]
write.table(submissao, file="arquivo_submissao-round1.csv", row.names=FALSE, sep=",")