15 Modelos
O objetivo deste capítulo é dar uma visão geral sobre a estrutura de modelos no R. Isto é, quais são as suas funções básicas, como especificar um modelo, recuperar resíduos, realizar predições etc. Esse processo é parte fundamental de análises mais aprofundadas. Os modelos podem ser usados, de maneira não exclusiva, para exploração de dados, geração de predições e análises de causalidade. Por exemplo:
- Descritivo: relação entre salários, idade, experiência e anos de estudo;
- Predição: modelo para identificar risco de fraude em uma transação bancária, classificação de imagens, previsão do PIB para o ano que vem;
- Causalidade: aumento de imposto sobre cigarro e redução no consumo.
15.1 Modelo Linear
Vamos introduzir a estrutura de modelos no R a partir de modelos lineares. Trataremos do modelo linear para regressão e do modelo de regressão logística para classificação. O modelo de regressão é utilizado quando a variável de interesse (dependente ou target) é uma variável quantitativa contínua. Por exemplo, salários, preços, notas em um exame etc. Por outro lado, modelos de classificação são utilizados quando a variável de interesse é categórica. Por exemplo: uma pessoa tem ou não tem a doença X, o cliente pagou ou não o cartão de crédito, o usuário X é um robô ou uma pessoa etc.
15.1.1 Regressão
Vamos começar com o modelo linear de regressão:
\[y_i = \beta_0 + \beta_1 x_{1i} + \beta_2 x_{2i} + ... + \beta_k x_{ki} + \epsilon_i, ~~ i = 1, ..., N,\] onde \(y\) é a variável dependente, \(x_{k}\) é a k-ésima variável explicativa, \(\beta_k\) é o parâmetro estimado para k-ésima variável e \(\epsilon\) é o termo de erro.
A função lm()
estima esse modelo pelo método denominado de mínimos quadrados ordinários (MQO). Antes de exemplificarmos o uso da função, vamos falar sobre a representação simbólica do modelo, ou seja, como especificar o modelo no R. Em geral, o modelo terá argumentos x
e y
, em que o usuário passa os dados nesses argumentos ou terá a estrutura de fórmula. Por ser o método menos usado no modelo linear, detalharemos a estrutura de fórmula. Na função lm()
, é obrigatório passar-se um objeto da classe fórmula, ou algum objeto que possa ser convertido para uma fórmula. Por exemplo: para o modelo linear com duas variáveis (\(y\) e \(x\)) e uma constante, a fórmula correspondente é:
<- 'y ~ x'
f class(f)
## [1] "character"
class(as.formula(f))
## [1] "formula"
Para mostrarmos as possibilidades de uso da fórmula de especificação do modelo, utilizaremos a base mtcars
. Esta base traz o consumo de gasolina (mpg
) e algumas outras características do veículo. Detalharemos cada variável explicativa conforme elas são usadas. No entanto, você pode olhar o help dessa base: ?mtcars
. Para iniciarmos, utilizaremos a variável mpg
(miles per galon) e a variável hp
(Gross horsepower).
data(mtcars)
lm(mpg ~ hp, data = mtcars)
##
## Call:
## lm(formula = mpg ~ hp, data = mtcars)
##
## Coefficients:
## (Intercept) hp
## 30.09886 -0.06823
Note que não houve especificação de uma constante. Automaticamente, o R inclui a constante. Você pode inclui-la explicitamente ou retirá-la:
lm(mpg ~ hp + 1, data = mtcars)
lm(mpg ~ hp + 0, data = mtcars)
Já temos uma pista de como incluir mais variáveis: basta “adicioná-las” com o símbolo +
. Isto é, vamos incluir a variável am
- Transmission (0 = automatic, 1 = manual) - no modelo:
lm(mpg ~ hp + am, data = mtcars)
Se quiséssemos incluir todas as variáveis explicativas:
lm(mpg ~ ., data = mtcars)
Interações:
lm(mpg ~ hp + am + hp:am, data = mtcars)
Transformações:
lm(log(mpg) ~ log(hp) + am, data = mtcars)
##
## Call:
## lm(formula = log(mpg) ~ log(hp) + am, data = mtcars)
##
## Coefficients:
## (Intercept) log(hp) am
## 5.1196 -0.4591 0.1954
No entanto, algumas transformações podem se confundir com símbolos quem são usados na fórmula. No exemplo abaixo, abstraia os dados e foque no efeito resultante da fórmula:
lm(mpg ~ (am + hp)^2 + hp^2, data = mtcars)
##
## Call:
## lm(formula = mpg ~ (am + hp)^2 + hp^2, data = mtcars)
##
## Coefficients:
## (Intercept) am hp am:hp
## 26.6248479 5.2176534 -0.0591370 0.0004029
(am + hp)^2
, em termos simbólicos, retorna am + hp + am*hp
e hp^2
retorna hp
. No caso em que um símbolo não pode ser usado diretamente, este deve ser usado dentro da função I()
:
lm(mpg ~ hp + I(hp^2), data = mtcars)
##
## Call:
## lm(formula = mpg ~ hp + I(hp^2), data = mtcars)
##
## Coefficients:
## (Intercept) hp I(hp^2)
## 40.4091172 -0.2133083 0.0004208
Variáveis categóricas são convertidas automaticamente para dummies. Por exemplo, vamos adicionar uma variável fictícia chamada cat
, que receberá valores a
, b
e c
ao data.frame mtcars
:
library(tidyverse)
<- mutate(mtcars,
mtcars cat = sample(c("a", "b", "c"),
size = nrow(mtcars), replace = TRUE))
lm(mpg ~ hp + cat, data = mtcars)
##
## Call:
## lm(formula = mpg ~ hp + cat, data = mtcars)
##
## Coefficients:
## (Intercept) hp catb catc
## 29.54197 -0.06645 -0.90406 1.85067
Falta agora discutir os principais argumentos da função lm()
:
lm(formula, data, subset, weights, na.action,
method = "qr", model = TRUE, x = FALSE, y = FALSE, qr = TRUE,
singular.ok = TRUE, contrasts = NULL, offset, ...)
O argumento formula
já foi discutido anteriormente. É neste argumento que o modelo é especificado. O argumento data
recebe (opcionalmente) um data.frame com os dados. O parâmetro data é opcional, porque você pode passar diretamente os vetores de dados. Por exemplo:
lm(log(mtcars$mpg) ~ log(mtcars$hp))
##
## Call:
## lm(formula = log(mtcars$mpg) ~ log(mtcars$hp))
##
## Coefficients:
## (Intercept) log(mtcars$hp)
## 5.5454 -0.5301
Continuando, há possibilidade de estimar-se o modelo para um subconjunto dos dados, sendo necessário informar um vetor que selecione as observações que entrarão na estimação, no argumento subset
. No exemplo que estamos utilizando, suponha que você queira estimar o modelo apenas para os carros automáticos:
lm(mpg ~ hp, data = mtcars, subset = (am == 0))
##
## Call:
## lm(formula = mpg ~ hp, data = mtcars, subset = (am == 0))
##
## Coefficients:
## (Intercept) hp
## 26.62485 -0.05914
lm(mpg ~ hp, data = mtcars, subset = (am == 1))
##
## Call:
## lm(formula = mpg ~ hp, data = mtcars, subset = (am == 1))
##
## Coefficients:
## (Intercept) hp
## 31.84250 -0.05873
Há também a possibilidade de utilizar-se um vetor de pesos no argumento weight
para a estimação de mínimos quadrados ordinários.
Para ver-se um sumário dos resultados da estimação, utiliza-se a função summary()
:
summary(lm(mpg ~ hp, data = mtcars))
##
## Call:
## lm(formula = mpg ~ hp, data = mtcars)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.7121 -2.1122 -0.8854 1.5819 8.2360
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 30.09886 1.63392 18.421 < 2e-16 ***
## hp -0.06823 0.01012 -6.742 1.79e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3.863 on 30 degrees of freedom
## Multiple R-squared: 0.6024, Adjusted R-squared: 0.5892
## F-statistic: 45.46 on 1 and 30 DF, p-value: 1.788e-07
15.1.1.1 Acessando os resultados
Além do resumo, é possível acessar uma série de objetos gerados pela função lm()
, como coeficientes, resíduos, valores preditos (dentro do conjunto de estimação) etc. Primeiro, vamos listar esses elementos:
<- lm(mpg ~ hp, data = mtcars)
fit is.list(fit)
## [1] TRUE
ls(fit)
## [1] "assign" "call" "coefficients" "df.residual" "effects" "fitted.values" "model"
## [8] "qr" "rank" "residuals" "terms" "xlevels"
Como se trata de uma lista, podemos acessar os objetos usando o $
.
$coefficients fit
## (Intercept) hp
## 30.09886054 -0.06822828
$residuals[1:10] fit
## Mazda RX4 Mazda RX4 Wag Datsun 710 Hornet 4 Drive Hornet Sportabout Valiant Duster 360
## -1.5937500 -1.5937500 -0.9536307 -1.1937500 0.5410881 -4.8348913 0.9170676
## Merc 240D Merc 230 Merc 280
## -1.4687073 -0.8171741 -2.5067823
Também existem funções para se acessar esses resultados:
coefficients(fit)
## (Intercept) hp
## 30.09886054 -0.06822828
residuals(fit)[1:5]
## Mazda RX4 Mazda RX4 Wag Datsun 710 Hornet 4 Drive Hornet Sportabout
## -1.5937500 -1.5937500 -0.9536307 -1.1937500 0.5410881
15.1.1.2 Predições
No R, para realizar-se predições, utiliza-se a função predict()
, que é uma função genérica. Isso significa que os seus argumentos e os valores retornados dependem da classe do objeto que estamos passando. No caso de um objeto da classe lm
, é suficiente passar o próprio objeto.
Abaixo está um exemplo do seu uso:
set.seed(13034) # para replicação
# 70% dos dados
<- sample(nrow(mtcars), size = 0.7*nrow(mtcars), replace = FALSE)
idx <- mtcars[idx, ]
train <- mtcars[-idx, ]
test
# 2 Modelos
<- lm(mpg ~ hp, data = train)
fit1 <- lm(mpg ~ hp + am + disp, data = train)
fit2
# Predições
<- predict(fit1, newdata = test[,-1])
pred1 <- predict(fit2, newdata = test[,-1])
pred2
# Comparando Root Mean Square Errors
library(ModelMetrics)
rmse(pred1, test[, "mpg"])
## [1] 4.958482
rmse(pred2, test[, "mpg"])
## [1] 3.568993
15.1.2 Classificação
Como já mencionado, quando a variável de interesse é categórica, utilizamos modelos de classificação. O modelo linear mais conhecido é o chamado Regressão Logística.
Suponha que queremos prever se uma pessoa irá ou não pagar a fatura do cartão de crédito. Definimos como \(p\) a probabilidade da pessoa não pagar e como razão de chance (_odds ratio) o valor \(\frac{p}{1-p}\). A função logit, por sua vez, é definida como:
\[ logit(p) = log\left(\frac{p}{1-p}\right)\]
Sendo \(y\) a nossa variável dependente, vamos definir que ela recebe valor 1 se o cliente não paga e 0 caso contrário. Logo, o modelo linear para o logit é definido como:
\[ logit(p(y = 1|X)) = \beta_0 + \beta_1 x_{1i} + \beta_2 x_{2i} + ... + \beta_k x_{ki}\]
Os parâmetros \(\beta's\) são obtidos a partir de métodos de otimização em que o objetivo minimizar é uma função de perda determinada. Note que a probabilidade de ocorrência do evento pode ser calculada como:
\[ p(y = 1|X) = \frac{e^{\beta_0 + \beta_1 x_{1i} + \beta_2 x_{2i} + ... + \beta_k x_{ki}}}{1 + e^{\beta_0 + \beta_1 x_{1i} + \beta_2 x_{2i} + ... + \beta_k x_{ki}}}\]
Um detalhe importante sobre a regressão logística é que este modelo se enquadra na classe de modelos lineares generalizados (generalized linear models - glm). Logo, este modelo pode ser estimado a partir da função glm()
, escolhendo a família binomial no argumento family
.
O exemplo a seguir vem do livro An Introduction to Statistical Learning with Application in R. Utilizaremos o pacote ISLR
e o conjunto de dados Smarket
(?Smarket
). Essa base traz informações sobre as variações do índice S&P 500 entre 2001 e 2005. Este índice é composto por 500 ativos negociados na NYSE ou Nasdaq.
library(ISLR)
data("Smarket")
head(Smarket)
## Year Lag1 Lag2 Lag3 Lag4 Lag5 Volume Today Direction
## 1 2001 0.381 -0.192 -2.624 -1.055 5.010 1.1913 0.959 Up
## 2 2001 0.959 0.381 -0.192 -2.624 -1.055 1.2965 1.032 Up
## 3 2001 1.032 0.959 0.381 -0.192 -2.624 1.4112 -0.623 Down
## 4 2001 -0.623 1.032 0.959 0.381 -0.192 1.2760 0.614 Up
## 5 2001 0.614 -0.623 1.032 0.959 0.381 1.2057 0.213 Up
## 6 2001 0.213 0.614 -0.623 1.032 0.959 1.3491 1.392 Up
A base consiste em nove variáveis. A variável de interesse é Direction
e outras cinco variáveis serão usadas como variáveis explicativas ou preditores. Inicialmente, separaremos nossos dados em treino e teste. Como trata-se de um problema de série temporal, utilizaremos a variável Year
para separar os dados.
<- Smarket %>%
train filter(Year <= 2004) %>%
select(-Year)
<- Smarket %>%
test filter(Year == 2005) %>%
select(-Year)
Agora vamos estimar o modelo:
<- glm(Direction ~ . -Today, data = train, family = binomial())
fit summary(fit)
##
## Call:
## glm(formula = Direction ~ . - Today, family = binomial(), data = train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.302 -1.190 1.079 1.160 1.350
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 0.191213 0.333690 0.573 0.567
## Lag1 -0.054178 0.051785 -1.046 0.295
## Lag2 -0.045805 0.051797 -0.884 0.377
## Lag3 0.007200 0.051644 0.139 0.889
## Lag4 0.006441 0.051706 0.125 0.901
## Lag5 -0.004223 0.051138 -0.083 0.934
## Volume -0.116257 0.239618 -0.485 0.628
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 1383.3 on 997 degrees of freedom
## Residual deviance: 1381.1 on 991 degrees of freedom
## AIC: 1395.1
##
## Number of Fisher Scoring iterations: 3
As predições são realizadas com a função predict()
, mas com o detalhe de que temos que escolher o tipo de predição. O default, link
, passará o logit. Isto é, o valor da predição linear. Já response
estimará a probabilidade da observação do evento de interesse. Por fim, terms
retorna uma matriz com a predição linear para cada variável explicativa. O nosso interesse é na probabilidade do mercado ter subido, logo, usaremos o tipo response
e transformaremos a probabilidade em Up
e Down
.
<- predict(fit, test, type = 'response')
pred <- ifelse(pred > 0.5, "Up", "Down")
pred <- factor(pred, levels = c("Down", "Up")) pred
Abaixo avaliamos o erro de classificação, que é de, aproximadamente, 52%. Ou seja, pior do que um chute aleatório.
# Taxa de erro
ce(test$Direction, pred)
## [1] 0.5198413
Os autores, então, sugerem estimar-se o modelo com apenas duas variáveis.
<- glm(Direction ~ Lag1 + Lag2, data = train, family = binomial())
fit <- predict(fit, test, type = 'response')
pred <- ifelse(pred > 0.5, "Up", "Down")
pred <- factor(pred, levels = c("Down", "Up"))
pred # Taxa de erro
ce(test$Direction, pred)
## [1] 0.4404762
Nesse caso, o modelo acertaria 56% das vezes.
15.1.3 Classificação com modelos baseados em árvores
Uma das limitações da regressão logística é que esse método não vai funcionar bem quando não for possível separar as classes linearmente, como no exemplo abaixo:
#### atencao: nao precisam se preocupar em reproduzir o codigo abaixo
# criar matriz de dados aleatorios
<- matrix(rnorm(5000, mean = 0, sd = 3), ncol = 2) %>% as.data.frame()
mat # criar elipse que circunda 25% dos pontos
<- car::dataEllipse(mat[, 1],
el 2],
mat[, levels = 0.25,
draw = FALSE)
# determinar se um ponto da matriz está dentro da elipse
$ta_dentro <- sp::point.in.polygon(mat[,1], mat[,2], el[,1], el[,2])
mat$ta_dentro <- as.factor(mat$ta_dentro)
mat
# construir grafico de pontos, colorindo a elipse
ggplot(mat, aes(x = V1, y = V2, color = ta_dentro)) +
geom_point()
Algoritmos baseados em Decision Trees tentam achar maneiras de criar subsets ou subgrupos do universo dos dados, onde cada subgrupo pertence a um node. O objetivo do modelo é criar nodes onde haja uma distinção clara entre as classes previstas de forma que possa a cada node a probabilidade de um indivíduo pertencer a uma classe. O gráfico abaixo é um exemplo simples e didática de uma árvore de decisão:
Nesse modelo, que tenta prever o sexo de uma pessoa baseada na altura e no peso, o algoritmo de classificação funciona como uma série de regras SE-NÃO:
- Se a altura for maior que 180cm, o indivíduo é um homem;
- Se a altura é menor ou igual a 180cm e o peso é maior que 80kg, o indivíduo é homem;
- Caso contrário, o indivíduo é mulher.
No R, os modelos de Decision Trees são aplicados principalmente pelo pacote rpart
. Este tutorial é bem explicativo.
15.1.3.1 Estudo de caso: coleta dos dados e análise exploratória
Como dataset de demonstração para a árvore de decisão, usaremos um bem interessante: Tentaremos entender o que leva um empregado a pedir ou não demissão de sua empresa. Entrem neste link, loguem no Kaggle e baixem o dataset para a mesma pasta de trabalho que estão usando no R.
O código abaixo mostra como importar e consolidar todos os datasets em um só dataframe:
Vejamos como ficou nosso conjunto de dados:
glimpse(rh)
## Rows: 4,300
## Columns: 30
## $ Age <dbl> 51, 31, 32, 38, 32, 46, 28, 29, 31, 25, 45, 55, 47, 28, 37, 21, 37, 35, 38, 26, 50, 53, 29, 55, …
## $ Attrition <fct> No, Yes, No, No, No, No, Yes, No, No, No, No, No, Yes, No, No, No, No, No, No, No, No, No, No, N…
## $ BusinessTravel <fct> Travel_Rarely, Travel_Frequently, Travel_Frequently, Non-Travel, Travel_Rarely, Travel_Rarely, T…
## $ Department <fct> Sales, Research & Development, Research & Development, Research & Development, Research & Develo…
## $ DistanceFromHome <dbl> 6, 10, 17, 2, 10, 8, 11, 18, 1, 7, 17, 14, 1, 1, 1, 3, 1, 7, 8, 1, 8, 11, 16, 1, 9, 5, 1, 2, 4, …
## $ Education <dbl> 2, 1, 4, 5, 1, 3, 2, 3, 3, 4, 2, 4, 1, 3, 3, 2, 3, 4, 3, 4, 4, 4, 4, 4, 3, 1, 2, 3, 3, 3, 1, 3, …
## $ EducationField <fct> Life Sciences, Life Sciences, Other, Life Sciences, Medical, Life Sciences, Medical, Life Scienc…
## $ EmployeeCount <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ EmployeeID <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 2…
## $ Gender <fct> Female, Female, Male, Male, Male, Female, Male, Male, Male, Female, Male, Female, Male, Male, Ma…
## $ JobLevel <dbl> 1, 1, 4, 3, 1, 4, 2, 2, 3, 4, 2, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 3, 1, 2, 3, …
## $ JobRole <fct> Healthcare Representative, Research Scientist, Sales Executive, Human Resources, Sales Executive…
## $ MaritalStatus <fct> Married, Single, Married, Married, Single, Married, Single, Married, Married, Divorced, Married,…
## $ MonthlyIncome <dbl> 131160, 41890, 193280, 83210, 23420, 40710, 58130, 31430, 20440, 134640, 79910, 55380, 57620, 25…
## $ NumCompaniesWorked <dbl> 1, 0, 1, 3, 4, 3, 2, 2, 0, 1, 0, 0, 1, 1, 4, 1, 2, 7, 1, 1, 3, 3, 1, 3, 1, 1, 3, 9, 2, 1, 9, 4, …
## $ Over18 <fct> Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, Y, …
## $ PercentSalaryHike <dbl> 11, 23, 15, 11, 12, 13, 20, 22, 21, 13, 13, 17, 11, 14, 11, 12, 13, 16, 11, 18, 23, 11, 11, 11, …
## $ StandardHours <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
## $ StockOptionLevel <dbl> 0, 1, 3, 3, 2, 0, 1, 3, 0, 1, 2, 0, 2, 0, 0, 3, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, …
## $ TotalWorkingYears <dbl> 1, 6, 5, 13, 9, 28, 5, 10, 10, 6, 21, 37, 10, 5, 7, 3, 15, 10, 8, 6, 28, 21, 10, 12, 5, 17, 19, …
## $ TrainingTimesLastYear <dbl> 6, 3, 2, 5, 2, 5, 2, 2, 2, 2, 2, 2, 4, 2, 2, 3, 2, 5, 5, 3, 2, 2, 2, 2, 3, 2, 2, 3, 5, 6, 2, 2, …
## $ YearsAtCompany <dbl> 1, 5, 5, 8, 6, 7, 0, 0, 9, 6, 20, 36, 10, 5, 5, 3, 5, 7, 8, 6, 10, 5, 10, 10, 5, 17, 1, 2, 3, 5,…
## $ YearsSinceLastPromotion <dbl> 0, 1, 0, 7, 0, 7, 0, 0, 7, 1, 4, 4, 9, 0, 0, 1, 0, 6, 7, 1, 1, 1, 0, 0, 3, 5, 0, 1, 0, 0, 1, 7, …
## $ YearsWithCurrManager <dbl> 0, 4, 3, 5, 4, 7, 0, 0, 8, 5, 10, 13, 9, 4, 1, 0, 2, 2, 7, 4, 6, 3, 9, 8, 3, 7, 0, 2, 2, 2, 2, 7…
## $ EnvironmentSatisfaction <dbl> 3, 3, 2, 4, 4, 3, 1, 1, 2, 2, 3, 4, 1, 4, 3, 4, 1, 2, 1, 3, 1, 3, 2, 2, 1, 4, 4, 4, 1, 4, 3, 3, …
## $ JobSatisfaction <dbl> 4, 2, 2, 4, 1, 2, 3, 2, 4, 1, 4, 1, 2, 4, 4, 3, 4, 2, 1, 2, 2, 3, 4, 4, 1, 4, 3, 4, 2, 4, 1, 2, …
## $ WorkLifeBalance <dbl> 2, 4, 1, 3, 3, 2, 1, 3, 3, 3, 3, 3, 2, 2, 4, 4, 3, 2, 3, 1, 2, 2, 2, 3, 3, 3, 1, 3, 3, 3, 3, 3, …
## $ JobInvolvement <dbl> 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 2, 3, 2, 3, 3, 2, 3, 2, 3, 3, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, …
## $ PerformanceRating <dbl> 3, 4, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, …
## $ tempo_medio_trabalho <dbl> 7.373651, 7.718969, 7.013240, 7.193678, 8.006175, 10.796096, 6.923570, 6.725621, 7.236499, 7.080…
Exercícios:
- Conheça melhor nossa variável Resposta. Qual o % da base que saiu da empresa (Attrition
= Yes). Baseado nessa informação, qual o pior modelo aceitável?
- Quais são as 2 variáveis numéricas que mais influenciam a variável resposta EmployeeID
? Façam gráficos para investigar essa relação.
- Façam o mesmo para duas variáveis categóricas.
15.1.3.2 Criação do modelo
Conforme já comentamos, uma etapa essencial de um projeto de modelagem é criar uma separação de conjuntos de treino e teste.
### separacao de conjuntos de treino e teste
# salvar em um objeto o tamanho vertical do dataframe
= nrow(rh)
n # sortear aleatoriamente 70% das linhas do dataframe para compor
# o conjunto de treino
<- sample(1:n, size = n * 0.7)
ind_treino
# criar conjunto de treino
<- rh[ind_treino, ]
rh_treino # criar conjunto de teste a partir da exclusao das linhas do conjunto de treino
<- rh[-ind_treino, ] rh_teste
A sintaxe para criar um modelo de Decision Tree no R é bem simples, sendo a mesma da Regressão. A única diferença é que a função usada é rpart
:
# construcao do modelo
<- rpart(Attrition ~ . - EmployeeID, data = rh_treino)
mod_arvore
# visualizar o modelo
::prp(mod_arvore, type = 4, extra = 6, fallen.leaves = FALSE, varlen = 0,
rpart.plotfaclen = 0, box.palette = "auto")
A árvore ficou meio grande, não? Uma boa alternativa nesse caso é salvar o gráfico da árvore em um pdf no computador:
# inicializar pdf em branco
pdf("minha_primeira_arvore.pdf")
# preencher pdf com o grafico da arvore
::prp(mod_arvore, type = 4, extra = 6, fallen.leaves = FALSE, varlen = 0,
rpart.plotfaclen = 0, box.palette = "auto")
# destravar a sessao para o pdf ser salvo
dev.off()
15.1.3.3 Avaliação do desempenho preditivo de um modelo de classificação:
Vamos avaliar a acurácia do modelo:
# criar vetor com previsoes
<- predict(mod_arvore, newdata = rh_teste, type = "class")
ycast <- rh_teste$Attrition
yreal
::confusionMatrix(data = ycast,
caretreference = yreal,
positive = "Yes",
mode = "everything")
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 1055 139
## Yes 23 73
##
## Accuracy : 0.8744
## 95% CI : (0.8551, 0.892)
## No Information Rate : 0.8357
## P-Value [Acc > NIR] : 6.138e-05
##
## Kappa : 0.414
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.34434
## Specificity : 0.97866
## Pos Pred Value : 0.76042
## Neg Pred Value : 0.88358
## Precision : 0.76042
## Recall : 0.34434
## F1 : 0.47403
## Prevalence : 0.16434
## Detection Rate : 0.05659
## Detection Prevalence : 0.07442
## Balanced Accuracy : 0.66150
##
## 'Positive' Class : Yes
##
Alguns dos conceitos mostrados acima são:
- Sensitivity ou Recall: De todos os casos que saíram de fato, quantos foram previstas corretamente?
- Specificity: De todos os casos que não saíram, quantos foram previstos corretamente?
- Accuracy: De todos os casos, quantos foram previstos corretamente?
- Precision: De todos os casos previstos que não iriam sair, quantos de fato não saíram?
- No Information Rate: Também conhecido como Null Error Rate. Qual teria sido a acurácia de um modelo que previsse sempre o caso mais comum?
15.1.3.4 Random Forest
O algoritmo de árvore de decisão, apesar de simples, serviu como base para modelos de árvores mais avançados, generalizáveis e flexíveis. Um deles é o Random Forest, que cria um número \(N\) de árvores a partir de uma amostragem de um número \(p2 < p1\) de colunas, onde \(p1\) é a quantidade total de colunas no conjunto de dados. Os dois livros referenciados são ótimas referências sobre esse algoritmo.
Criar um modelo de Random Forest no R também é muito fácil.
# construir modelo
<- randomForest::randomForest(Attrition ~ . - EmployeeID,
mod_rf data = rh_treino,
importance = TRUE)
# obter previsoes a partir do modelo
<- predict(mod_rf, newdata = rh_teste, type = "class")
yhat_rf # obter matriz de confusao
::confusionMatrix(data = yhat_rf, reference = yreal) caret
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 1078 9
## Yes 0 203
##
## Accuracy : 0.993
## 95% CI : (0.9868, 0.9968)
## No Information Rate : 0.8357
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9742
##
## Mcnemar's Test P-Value : 0.007661
##
## Sensitivity : 1.0000
## Specificity : 0.9575
## Pos Pred Value : 0.9917
## Neg Pred Value : 1.0000
## Prevalence : 0.8357
## Detection Rate : 0.8357
## Detection Prevalence : 0.8426
## Balanced Accuracy : 0.9788
##
## 'Positive' Class : No
##
Um recurso muito interessante do modelo Random Forest é o cálculo da importância de cada variável, que é medido com base no impacto de realizar permutações de cada variável explanatória usada no modelo sobre alguma métrica de acurácia. O gráfico abaixo mostra que a maior perda de perfomance preditiva ocorre quando a variável de tempo no trabalho é permutada aleatoriamente:
varImpPlot(mod_rf,
# especificar que a medida de importancia é a perda media de acuracia
type = 1,
main = "Importância de cada variável explan. no modelo de RF")
O modelo Random Forest também pode ser usado de maneira probabilística, ou seja, mudando o output para a probabilidade de ele ser sim:
<- predict(mod_rf, newdata = rh_teste, type = "prob")
yhat_rf_prob
head(yhat_rf_prob)
## No Yes
## 1 0.970 0.030
## 2 0.990 0.010
## 3 0.988 0.012
## 4 0.984 0.016
## 5 0.984 0.016
## 6 0.206 0.794
15.2 Exercícios
Utilizando a base de dados
Wage
, do pacoteISLR
, crie dois data.frames: um com 70% dos dados (train
) e outro com 30% (test
).Crie um novo objeto chamado
fit
, a partir da funçãolm()
. Use como variável dependente (\(Y\)) a colunalogwage
e escolha outras três colunas como variáveis explicativas.Compute as predições desse modelo utilizando a função
predict()
.Compute a raiz do erro quadrático médio (rmse). (
ModelMetrics::rmse()
).Inclua outras variáveis e cheque o que acontece com o
rmse
.
library(ISLR)
library(ModelMetrics)
<- sample(nrow(Wage), 0.7 * nrow(Wage))
idx <- Wage[idx, ]
train <- Wage[idx, ]
test
<- lm(logwage ~ age + education + maritl + health_ins, data = train)
fit <- predict(fit, test)
pred
rmse(actual = test$logwage, predicted = pred)
## [1] 0.2777082