A regressão logística é usada para prever uma classe, ou seja, uma probabilidade. A regressão logística pode prever um resultado binário com precisão.
Imagine que você deseja prever se um empréstimo será negado/aceito com base em muitos atributos. Neste cao, a resposta na regressão logística é da forma 0/1. \(Y = 0\) se um empréstimo for rejeitado e \(Y = 1\) se aceito, dizemos então, que o sucesso acontecerá quando um epressário é aceito. Observemos que nada impede de que o sucesso seja exatamente o contrário; tudo depende do interesse da pesquisa.
Um modelo de regressão logística difere do modelo de regressão linear de duas maneiras:
Em segundo lugar, o resultado é medido pela seguinte função de ligação probabilística chamada sigmóide devido à sua forma de S:
sig = function(t) 1/(1+exp(-t))
t = seq(-10,10,by=0.01)
plot(t, sig(t), type = "l", col = "red", ylab = expression(frac(1,1+e^(-t))))
grid()
Observemos que a função sigmóide retorna valores entre 0 a 1. Para a tarefa de classificação, precisamos de uma saída discreta de 0 ou 1.
par(mfrow=c(1,2))
t = seq(9,10,by=0.01)
plot(t, sig(t), type = "l", col = "red", ylab = expression(frac(1,1+e^(-t))))
grid()
t = seq(-10,-9,by=0.01)
plot(t, sig(t), type = "l", col = "red", ylab = expression(frac(1,1+e^(-t))))
grid()
Para converter um fluxo contínuo em valor discreto, podemos definir um limite de decisão em 0.5, ou seja, \[\begin{equation*} \widehat{Y}=\left\{ \begin{array}{rcl} 1, & \mbox{ se } & \widehat{\mu}>0.5 \\[0.8em] 0, & \mbox{ se } & \widehat{\mu}<0.5 \end{array}\right.\cdot \end{equation*}\] Todos os valores acima deste limite serão classificados como 1 e 0, caso contrário. Para outro critério de classificação utilizamos a curva ROC.
Vamos usar um conjunto de dados de adultos para ilustrar a regressão logística. O “adulto” é um ótimo conjunto de dados para a tarefa de classificação. O objetivo é prever se a renda anual em dólares de um indivíduo será superior a 50.000.
O conjunto de dados contém 46.033 observações e dez recursos:
library(dplyr)
data_adult <- read.csv("https://raw.githubusercontent.com/guru99-edu/R-Programming/master/adult.csv")
glimpse(data_adult)
## Rows: 48,842
## Columns: 10
## $ x <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,~
## $ age <int> 25, 38, 28, 44, 18, 34, 29, 63, 24, 55, 65, 36, 26, 58~
## $ workclass <chr> "Private", "Private", "Local-gov", "Private", "?", "Pr~
## $ education <chr> "11th", "HS-grad", "Assoc-acdm", "Some-college", "Some~
## $ educational.num <int> 7, 9, 12, 10, 10, 6, 9, 15, 10, 4, 9, 13, 9, 9, 9, 14,~
## $ marital.status <chr> "Never-married", "Married-civ-spouse", "Married-civ-sp~
## $ race <chr> "Black", "White", "White", "Black", "White", "White", ~
## $ gender <chr> "Male", "Male", "Male", "Male", "Female", "Male", "Mal~
## $ hours.per.week <int> 40, 50, 40, 40, 30, 30, 40, 32, 40, 10, 40, 40, 39, 35~
## $ income <chr> "<=50K", "<=50K", ">50K", ">50K", "<=50K", "<=50K", "<~
Vamos proceder da seguinte forma:
Neste tutorial, cada etapa será detalhada para realizar uma análise em um conjunto de dados real.
continuous <-select_if(data_adult, is.numeric)
summary(continuous)
## x age educational.num hours.per.week
## Min. : 1 Min. :17.00 Min. : 1.00 Min. : 1.00
## 1st Qu.:12211 1st Qu.:28.00 1st Qu.: 9.00 1st Qu.:40.00
## Median :24422 Median :37.00 Median :10.00 Median :40.00
## Mean :24422 Mean :38.64 Mean :10.08 Mean :40.42
## 3rd Qu.:36632 3rd Qu.:48.00 3rd Qu.:12.00 3rd Qu.:45.00
## Max. :48842 Max. :90.00 Max. :16.00 Max. :99.00
Explicação do código:
Na tabela acima, podemos ver que os dados têm escalas e horas totalmente diferentes hours.per.weeks tem grandes valores discrepantes, ou seja, observe o último quartil e o valor máximo.
Podemos lidar com isso seguindo duas etapas:
Vamos examinar mais de perto a distribuição de hours.per.week:
# Histograma com curva de densidade estimada kernel
library(ggplot2)
ggplot(continuous, aes(x = hours.per.week)) + geom_density(alpha = .2, fill = "#FF6666")
A variável tem muitos outliers e uma distribuição não bem definida. Pode-se resolver parcialmente esse problema excluindo os primeiros 0.01% das horas semanais.
Sintaxe básica do quantil:
top_one_percent <- quantile(data_adult$hours.per.week, .99)
top_one_percent
## 99%
## 80
98 por cento da população trabalha menos de 80 horas por semana. Assim calculamos o percentil 2 por cento superior.
Explicação do código:
Você pode diminuir as observações acima deste limite. Você usa o filtro da biblioteca dplyr.
data_adult_drop <-data_adult %>% filter(hours.per.week<top_one_percent)
dim(data_adult_drop)
## [1] 48314 10
2: Padronizando as variáveis contínuas
Você pode padronizar cada coluna para melhorar o desempenho porque seus dados não têm a mesma escala. Você pode usar a função mutate_if da biblioteca dplyr.
Podemos padronizar as colunas numéricas da seguinte forma:
data_adult_rescale <- data_adult_drop %>% mutate_if(is.numeric, funs(as.numeric(scale(.))))
## Warning: `funs()` was deprecated in dplyr 0.8.0.
## Please use a list of either functions or lambdas:
##
## # Simple named list:
## list(mean = mean, median = median)
##
## # Auto named with `tibble::lst()`:
## tibble::lst(mean, median)
##
## # Using lambdas
## list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
head(data_adult_rescale)
## x age workclass education educational.num
## 1 -1.732661 -0.99161306 Private 11th -1.19830033
## 2 -1.732590 -0.04464061 Private HS-grad -0.41917209
## 3 -1.732519 -0.77308096 Local-gov Assoc-acdm 0.74952027
## 4 -1.732448 0.39242361 Private Some-college -0.02960797
## 5 -1.732377 -1.50152131 ? Some-college -0.02960797
## 6 -1.732306 -0.33601675 Private 10th -1.58786445
## marital.status race gender hours.per.week income
## 1 Never-married Black Male 0.008216002 <=50K
## 2 Married-civ-spouse White Male 0.885642886 <=50K
## 3 Married-civ-spouse White Male 0.008216002 >50K
## 4 Married-civ-spouse Black Male 0.008216002 >50K
## 5 Never-married White Female -0.869210882 <=50K
## 6 Never-married White Male -0.869210882 <=50K
Explicação do código:
Esta etapa tem dois objetivos:
Vamos dividir esta etapa em três partes:
Podemos selecionar as colunas fatores com o código abaixo:
# Selecionando as coluna categóricas
factor <- data.frame(select_if(data_adult_rescale, is.character))
ncol(factor)
## [1] 6
head(factor)
## workclass education marital.status race gender income
## 1 Private 11th Never-married Black Male <=50K
## 2 Private HS-grad Married-civ-spouse White Male <=50K
## 3 Local-gov Assoc-acdm Married-civ-spouse White Male >50K
## 4 Private Some-college Married-civ-spouse Black Male >50K
## 5 ? Some-college Never-married White Female <=50K
## 6 Private 10th Never-married White Male <=50K
As variáveis identificadas são to tipo character, o que não significa sejam fatores. As transformamos em fatores da seguinte forma:
factor <- as.data.frame(lapply(factor, as.factor))
str(factor)
## 'data.frame': 48314 obs. of 6 variables:
## $ workclass : Factor w/ 9 levels "?","Federal-gov",..: 5 5 3 5 1 5 1 7 5 5 ...
## $ education : Factor w/ 16 levels "10th","11th",..: 2 12 8 16 16 1 12 15 16 6 ...
## $ marital.status: Factor w/ 7 levels "Divorced","Married-AF-spouse",..: 5 3 3 3 5 5 5 3 5 3 ...
## $ race : Factor w/ 5 levels "Amer-Indian-Eskimo",..: 3 5 5 3 5 5 3 5 5 5 ...
## $ gender : Factor w/ 2 levels "Female","Male": 2 2 2 2 1 2 2 2 1 2 ...
## $ income : Factor w/ 2 levels "<=50K",">50K": 1 1 2 2 1 1 1 2 1 1 ...
Explicação do código
O conjunto de dados contém 6 variáveis categóricas. A segunda etapa é mais habilidosa. Desejamos traçar um gráfico de barras para cada coluna no fator do quadro de dados. É mais conveniente automatizar o processo, especialmente quando há muitas colunas.
# Criando um gráfico para cada coluna
graph <- lapply(names(factor), function(x)
ggplot(factor, aes(get(x))) + geom_bar() + xlab((x)) + theme(axis.text.x = element_text(angle = 90)))
Explicação do código
A última etapa é relativamente fácil. Você deseja imprimir os 6 gráficos.
# Imprimindo os gráficos
graph
## [[1]]
##
## [[2]]
##
## [[3]]
##
## [[4]]
##
## [[5]]
##
## [[6]]
Reforma da educação
No gráfico acima, você pode ver que a variável education (educação) possui 16 níveis. Isso é substancial e alguns níveis têm um número relativamente baixo de observações. Se você deseja melhorar a quantidade de informações que pode obter dessa variável, pode reformulá-la para um nível superior. Ou seja, você cria grupos maiores (em número de observações) com nível de educação semelhante. Por exemplo, baixo nível de educação será convertido em evasão. Os níveis mais elevados de educação serão alterados para mestre.Nível antigo | Novo nível |
---|---|
Preschool | Dropout |
10th | Dropout |
11th | Dropout |
12th | Dropout |
1st-4th | Dropout |
5th-6th | Dropout |
7th-8th | Dropout |
9th | Dropout |
HS-Grad | HighGrad |
Some-college | Community |
Assoc-acdm | Community |
Assoc-voc | Community |
Bachelors | Bachelors |
Masters | Masters |
Doctorate | PhD |
Para fazermos a transformação sugerida utilizamos o seguinte código:
recast_data <- data_adult_rescale %>% select(-x) %>% mutate(education =
factor(ifelse(education == "Preschool" | education == "10th" | education == "11th" |
education == "12th" | education == "1st-4th" | education == "5th-6th" |
education == "7th-8th" | education == "9th", "Dropout",
ifelse(education == "HS-grad", "HighGrad", ifelse(education == "Some-college" |
education == "Assoc-acdm" | education == "Assoc-voc", "Community",
ifelse(education == "Bachelors", "Bachelors",
ifelse(education == "Masters" | education == "Prof-school", "Master", "PhD")))))))
Explicação do código:
recast_data %>%
group_by(education) %>%
summarize(average_educ_year = mean(educational.num), count = n()) %>% arrange(average_educ_year)
## # A tibble: 6 x 3
## education average_educ_year count
## <fct> <dbl> <int>
## 1 Dropout -1.74 6340
## 2 HighGrad -0.419 15608
## 3 Community 0.111 14396
## 4 Bachelors 1.14 7968
## 5 Master 1.62 3427
## 6 PhD 2.31 575
Reformulação do estado civil:
Nível antigo | Novo nível |
---|---|
Never-married | Not-married |
Married-spouse-absent | Not-married |
Married-AF-spouse | Married |
Married-civ-spouse | Married |
Separated | Separated |
Divorced | Separated |
Widows | Widow |
# Mudando o nível de casamento
recast_data <- recast_data %>% mutate(marital.status =
factor(ifelse(marital.status == "Never-married" |
marital.status == "Married-spouse-absent", "Not_married",
ifelse(marital.status == "Married-AF-spouse" |
marital.status == "Married-civ-spouse", "Married",
ifelse(marital.status == "Separated" | marital.status == "Divorced", "Separated",
"Widow")))))
Você pode verificar o número de indivíduos em cada grupo:
table(recast_data$marital.status)
##
## Married Not_married Separated Widow
## 22085 16638 8084 1507
É hora de verificar algumas estatísticas sobre nossas variáveis de destino. No gráfico abaixo, você conta a porcentagem de indivíduos que ganham mais de 50 mil de acordo com seu gênero.
#Renda vs gênero
ggplot(recast_data, aes(x = gender, fill = income)) + geom_bar(position = "fill") + theme_classic()
A seguir, verificamos se a origem do indivíduo afeta seus ganhos.
# Receita segundo a origem
ggplot(recast_data, aes(x = race, fill = income)) +
geom_bar(position = "fill") + theme_classic() + theme(axis.text.x = element_text(angle = 90))
O número de horas de trabalho por gênero.
# box plot gênero vs tempo de trabalho
ggplot(recast_data, aes(x = gender, y = hours.per.week)) +
geom_boxplot() + stat_summary(fun.y = mean, geom = "point", size = 3, color = "steelblue") +
theme_classic()
## Warning: `fun.y` is deprecated. Use `fun` instead.
O gráfico acima confirma que a distribuição do tempo de trabalho se ajusta a grupos diferentes. No box plot, ambos os gêneros não apresentam observações homogêneas.
Você pode verificar a densidade do tempo de trabalho semanal por tipo de ensino. As distribuições têm muitas opções distintas. Provavelmente, isso pode ser explicado pelo tipo de contrato nos EUA.
# Traçar a distribuição do tempo de trabalho segundo educação
ggplot(recast_data, aes(x = hours.per.week)) +
geom_density(aes(color = education), alpha = 0.5) + theme_classic()
Para confirmar suas idéias, você pode realizar um teste ANOVA unilateral:
anova <- aov(hours.per.week~education, recast_data)
summary(anova)
## Df Sum Sq Mean Sq F value Pr(>F)
## education 5 1637 327.5 338.9 <2e-16 ***
## Residuals 48308 46676 1.0
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
O teste ANOVA confirma a diferença de média entre os grupos.
Antes de executar o modelo, podemos ver se o número de horas trabalhadas está relacionado à idade.
ggplot(recast_data, aes(x = age, y = hours.per.week)) +
geom_point(aes(color = income), size = 0.5) + stat_smooth(method = 'lm',
formula = y~poly(x, 2), se = TRUE, aes(color = income)) + theme_classic()
Explicação do código
Em suma, você pode testar os termos de interação no modelo para captar o efeito da não linearidade entre o tempo de trabalho semanal e outros recursos. É importante detectar em que condições o tempo de trabalho difere.
A próxima verificação é visualizar a correlação entre as variáveis. Você converte o tipo de nível de fator em numérico para que possa plotar um mapa de calor contendo o coeficiente de correlação calculado com o método de Spearman.
head(recast_data)
## age workclass education educational.num marital.status race gender
## 1 -0.99161306 Private Dropout -1.19830033 Not_married Black Male
## 2 -0.04464061 Private HighGrad -0.41917209 Married White Male
## 3 -0.77308096 Local-gov Community 0.74952027 Married White Male
## 4 0.39242361 Private Community -0.02960797 Married Black Male
## 5 -1.50152131 ? Community -0.02960797 Not_married White Female
## 6 -0.33601675 Private Dropout -1.58786445 Not_married White Male
## hours.per.week income
## 1 0.008216002 <=50K
## 2 0.885642886 <=50K
## 3 0.008216002 >50K
## 4 0.008216002 >50K
## 5 -0.869210882 <=50K
## 6 -0.869210882 <=50K
wc <- factor(recast_data$workclass, labels = 1:9)
ed <- factor(recast_data$education, labels = 1:6)
ms <- factor(recast_data$marital.status, labels = 1:4)
rc <- factor(recast_data$race, labels = 1:5)
ge <- factor(recast_data$gender, labels = 1:2)
nc <- factor(recast_data$income, labels = 1:2)
recast_data1 <- as.data.frame(cbind(age = recast_data$age, workclass = wc, education = ed,
education.num = recast_data$educational.num, marital.status= ms,
race = rc, gender = ge, hours.per.week = recast_data$hours.per.week, income = nc))
head(recast_data1)
## age workclass education education.num marital.status race gender
## 1 -0.99161306 5 3 -1.19830033 2 3 2
## 2 -0.04464061 5 4 -0.41917209 1 5 2
## 3 -0.77308096 3 2 0.74952027 1 5 2
## 4 0.39242361 5 2 -0.02960797 1 3 2
## 5 -1.50152131 1 2 -0.02960797 2 5 1
## 6 -0.33601675 5 3 -1.58786445 2 5 2
## hours.per.week income
## 1 0.008216002 1
## 2 0.885642886 1
## 3 0.008216002 2
## 4 0.008216002 2
## 5 -0.869210882 1
## 6 -0.869210882 1
library(GGally)
ggcorr(recast_data1, method = c("pairwise", "spearman"),
nbreaks = 6, hjust = 0.8, label = TRUE, label_size = 3, color = "grey50")
Explicação do código:
Qualquer tarefa de aprendizado de máquina supervisionada exige a divisão dos dados entre um conjunto de treino ou ensinamento e um conjunto de teste.
set.seed(1234)
create_train_test <- function(data, size = 0.8, train = TRUE) {
n_row = nrow(data)
total_row = size * n_row
train_sample <- 1: total_row
if (train == TRUE) {
return (data[train_sample, ])
} else {
return (data[-train_sample, ])
}
}
data_train <- create_train_test(recast_data, 0.8, train = TRUE)
data_test <- create_train_test(recast_data, 0.8, train = FALSE)
dim(data_train)
## [1] 38651 9
dim(data_test)
## [1] 9663 9
O Modelo Linear Generalizado é uma coleção de modelos e para estimar o modelo logístico e obter a cisão no nível de renda com base num conjunto de recursos.
formula <- factor(income) ~ .
logit <- glm(formula, data = data_train, family = 'binomial')
summary(logit)
##
## Call:
## glm(formula = formula, family = "binomial", data = data_train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.6426 -0.5653 -0.2533 -0.0727 3.2517
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.28482 0.22017 -5.836 5.36e-09 ***
## age 0.42214 0.01877 22.492 < 2e-16 ***
## workclassFederal-gov 1.33598 0.11827 11.296 < 2e-16 ***
## workclassLocal-gov 0.69509 0.10545 6.592 4.35e-11 ***
## workclassNever-worked -5.52301 72.34065 -0.076 0.93914
## workclassPrivate 0.79941 0.09261 8.632 < 2e-16 ***
## workclassSelf-emp-inc 1.26046 0.11408 11.049 < 2e-16 ***
## workclassSelf-emp-not-inc 0.24939 0.10280 2.426 0.01527 *
## workclassState-gov 0.52998 0.11633 4.556 5.22e-06 ***
## workclassWithout-pay 0.23184 0.86794 0.267 0.78938
## educationCommunity -0.42581 0.08142 -5.230 1.70e-07 ***
## educationDropout -1.03575 0.20982 -4.936 7.96e-07 ***
## educationHighGrad -0.65998 0.11640 -5.670 1.43e-08 ***
## educationMaster 0.34846 0.06672 5.222 1.77e-07 ***
## educationPhD 0.46896 0.15539 3.018 0.00255 **
## educational.num 0.57696 0.06968 8.280 < 2e-16 ***
## marital.statusNot_married -2.52272 0.05058 -49.880 < 2e-16 ***
## marital.statusSeparated -2.17079 0.05369 -40.433 < 2e-16 ***
## marital.statusWidow -2.26364 0.12177 -18.589 < 2e-16 ***
## raceAsian-Pac-Islander 0.10016 0.20012 0.501 0.61671
## raceBlack 0.08880 0.19012 0.467 0.64046
## raceOther 0.05968 0.27083 0.220 0.82560
## raceWhite 0.36501 0.18136 2.013 0.04415 *
## genderMale 0.05616 0.04205 1.335 0.18174
## hours.per.week 0.43088 0.01749 24.633 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 42290 on 38650 degrees of freedom
## Residual deviance: 27938 on 38626 degrees of freedom
## AIC: 27988
##
## Number of Fisher Scoring iterations: 10
Explicação do código:
O resumo do nosso modelo revela informações interessantes. O desempenho de uma regressão logística é avaliado com métricas chave específicas.
# A lista é muito longa, imprima apenas os três primeiros elementos
lapply(logit, class)[1:3]
## $coefficients
## [1] "numeric"
##
## $residuals
## [1] "numeric"
##
## $fitted.values
## [1] "numeric"
Cada valor pode ser extraído com o sinal $ seguido do nome das métricas. Por exemplo, armazenamos o modelo como logit. Para extrair os critérios AIC, você usa:
logit$aic
## [1] 27987.98
Como estudamos, o \(R^2_V\) pode ser obtido da seguinte forma:
library(rsq)
## Warning: package 'rsq' was built under R version 4.0.5
rsq(logit)
## [1] 0.3563337
A matriz de confusão é a melhor escolha para avaliar o desempenho da classificação em comparação com as diferentes métricas que vimos antes. A ideia geral é contar quantas vezes as instâncias TRUE são classificadas como FALSE.
Matriz de confusão | PREDITOS | |||||
---|---|---|---|---|---|---|
FALSE | TRUE | |||||
ATUAL | FALSE | Verdadeiro negativo (TN) | Falso positivo (FP) | |||
TRUE | Falso negativo (FN) | Verdadeiro positivo (TP) |
Para calcular a matriz de confusão, primeiro você precisa ter um conjunto de previsões para que possam ser comparadas aos alvos reais.
predict <- predict(logit, data_test, type = 'response')
# Matriz de confusão
table_mat <- table(data_test$income, predict > 0.5)
table_mat
##
## FALSE TRUE
## <=50K 6796 506
## >50K 1112 1249
Explicação do código:
Cada linha em uma matriz de confusão representa um alvo real, enquanto cada coluna representa um alvo previsto. A primeira linha desta matriz considera a renda inferior a 50k a classe FALSE (Falsa): 6861 foram corretamente classificados como indivíduos com renda inferior a 50k (Verdadeiro negativo), enquanto o restante foi classificado erroneamente como acima de 50k (Falso positivo). A segunda linha considera a receita acima de 50k, a classe positiva foi 1260 (Verdadeiro positivo), enquanto a Verdadeira negativa foi 1144.
Você pode calcular a precisão do modelo somando o verdadeiro positivo + verdadeiro negativo sobre a observação total \[\begin{equation*} precisão = \dfrac{TP+TN}{TP+TN+FP+FN}\cdot \end{equation*}\]
accuracy_Test <- sum(diag(table_mat)) / sum(table_mat)
accuracy_Test
## [1] 0.8325572
Explicação do código:
O modelo parece ter um problema: ele superestima o número de falsos negativos. Isso é chamado de paradoxo do teste de precisão. Declaramos que a precisão é a razão entre as previsões corretas e o número total de casos. Podemos ter uma precisão relativamente alta, mas um modelo inútil. Acontece quando existe uma classe dominante. Se você olhar novamente para a matriz de confusão, verá que a maioria dos casos são classificados como verdadeiros negativos. Imagine agora, o modelo classificou todas as classes como negativas (ou seja, abaixo de 50k). Você teria uma precisão de 75 por cento (6796+506)/(6796+506+1112+1249). Seu modelo tem um desempenho melhor, mas se esforça para distinguir o verdadeiro positivo do verdadeiro negativo.
Nessa situação, é preferível ter uma métrica mais concisa. Podemos olhar para:
A precisão analisa a precisão da previsão positiva. Recall é a proporção de instâncias positivas que são detectadas corretamente pelo classificador.
Pode-se construir duas funções para calcular essas duas métricas:
Construção da precisão:
precision <- function(matrix) {
# True positive
tp <- matrix[2, 2]
# false positive
fp <- matrix[1, 2]
return (tp / (tp + fp))
}
Explicação do código:
Construção do recall:
recall <- function(matrix) {
# true positive
tp <- matrix[2, 2]# false positive
fn <- matrix[2, 1]
return (tp / (tp + fn))
}
Explicação do código
Podemos testar as funções:
prec <- precision(table_mat)
prec
## [1] 0.7116809
rec <- recall(table_mat)
rec
## [1] 0.5290131
Quando o modelo diz que é um indivíduo acima de 50k, ele está correto em apenas 53% dos casos e pode reivindicar indivíduos acima de 50k em 71% dos casos.
Você pode criar a \(F_1\) pontuação (score) com base na precisão e na recuperação (recall). O \(F_1\) é uma média harmônica dessas duas métricas, o que significa que dá mais peso aos valores mais baixos: \[\begin{equation*} F_1=2*\dfrac{\mbox{Precisão}\times \mbox{Recall}}{\mbox{Precisão}+\mbox{Recall}}\cdot \end{equation*}\]
f1 <- 2 * ((prec * rec) / (prec + rec))
f1
## [1] 0.6068999
É impossível ter ambos, alta precisão e alto recall.
Se aumentarmos a precisão, o indivíduo correto será melhor previsto, mas perderíamos muitos deles (menor recall). Em algumas situações, preferimos maior precisão do que recall. Existe uma relação côncava entre precisão e recall.
A curva Receiver Operating Characteristic é outra ferramenta comum usada com classificação binária. É muito semelhante à curva de precisão / rechamada, mas em vez de representar a precisão versus rechamada, a curva ROC mostra a taxa de verdadeiro positivo (ou seja, rechamada) contra a taxa de falso positivo. A taxa de falsos positivos é a proporção de instâncias negativas classificadas incorretamente como positivas. É igual a um menos a taxa negativa verdadeira. A verdadeira taxa negativa também é chamada de especificidade. Portanto, a curva ROC representa a sensibilidade (recall) versus 1-especificidade
Podemos plotar o ROC com as funções prediction() e performance():
library(ROCR)
ROCRpred <- prediction(predict, data_test$income)
ROCRperf <- performance(ROCRpred, 'tpr', 'fpr')
plot(ROCRperf, colorize = TRUE, text.adj = c(-0.2, 1.7))
grid()
Explicação do código:
Você pode tentar adicionar não linearidade ao modelo com a interação entre:
formula_2 <- factor(income)~age: hours.per.week + gender: hours.per.week + .
logit_2 <- glm(formula_2, data = data_train, family = 'binomial')
predict_2 <- predict(logit_2, data_test, type = 'response')
table_mat_2 <- table(data_test$income, predict_2 > 0.5)
precision_2 <- precision(table_mat_2)
recall_2 <- recall(table_mat_2)
f1_2 <- 2 * ((precision_2 * recall_2) / (precision_2 + recall_2))
f1_2
## [1] 0.6069869
A pontuação é ligeiramente superior à anterior. Você pode continuar trabalhando nos dados e tentar bater a pontuação.