Para fazer o download do script R, da escrita markdown em pdf e html, acesse meu repositório no Github.
Nosso objetivo é exercitar UM algoritmo de redução de dimensão (Mínimos Quadrados Parciais) e outro de regularização e encolhimento de estimadores da regressão linear: o (Elastic Net), que faz uma combinação linear entre a penalização Lasso (\(\alpha =1\)) e Ridge (\(\alpha =0\)) com o parâmetro mixture
, na estrutura do Tidymodels. E \(\lambda\) sendo a importância da regularização, ou o grau de penalização, que é o parâmetro penalty
. A estrutura do Elastic Net é:
\[\min_{\beta_0,\beta} \frac{1}{N} \sum_{i=1}^{N} w_i l(y_i,\beta_0+\beta^T x_i) + \lambda\left[(1-\alpha)\|\beta\|_2^2/2 + \alpha \|\beta\|_1\right],\]
Para importar os dados, vamos já retirar os NA
usando na.omit()
.
setwd('/home/heitor/Área de Trabalho/R Projects/Análise Macro/Labs/Lab 14')
library(tidyverse) # padrão manipulação e visualização
library(tidymodels) # padrão para ML
library(glmnet) # Elastic Net
library(plsmod) # Partial Least Squares
library(plotly) # Gráficos interativos
library(GGally) # Gráfico de Correlação
library(ISLR) # Base de Dados Hitters
library(rmarkdown) # Tabelas paginadas
set.seed(2022) # Aleatoriedade fixa em número auspicioso
<- as.data.frame(Hitters) %>%
dds as_tibble() %>% na.omit()
%>% summary() dds
## AtBat Hits HmRun Runs
## Min. : 19.0 Min. : 1.0 Min. : 0.00 Min. : 0.00
## 1st Qu.:282.5 1st Qu.: 71.5 1st Qu.: 5.00 1st Qu.: 33.50
## Median :413.0 Median :103.0 Median : 9.00 Median : 52.00
## Mean :403.6 Mean :107.8 Mean :11.62 Mean : 54.75
## 3rd Qu.:526.0 3rd Qu.:141.5 3rd Qu.:18.00 3rd Qu.: 73.00
## Max. :687.0 Max. :238.0 Max. :40.00 Max. :130.00
## RBI Walks Years CAtBat
## Min. : 0.00 Min. : 0.00 Min. : 1.000 Min. : 19.0
## 1st Qu.: 30.00 1st Qu.: 23.00 1st Qu.: 4.000 1st Qu.: 842.5
## Median : 47.00 Median : 37.00 Median : 6.000 Median : 1931.0
## Mean : 51.49 Mean : 41.11 Mean : 7.312 Mean : 2657.5
## 3rd Qu.: 71.00 3rd Qu.: 57.00 3rd Qu.:10.000 3rd Qu.: 3890.5
## Max. :121.00 Max. :105.00 Max. :24.000 Max. :14053.0
## CHits CHmRun CRuns CRBI
## Min. : 4.0 Min. : 0.00 Min. : 2.0 Min. : 3.0
## 1st Qu.: 212.0 1st Qu.: 15.00 1st Qu.: 105.5 1st Qu.: 95.0
## Median : 516.0 Median : 40.00 Median : 250.0 Median : 230.0
## Mean : 722.2 Mean : 69.24 Mean : 361.2 Mean : 330.4
## 3rd Qu.:1054.0 3rd Qu.: 92.50 3rd Qu.: 497.5 3rd Qu.: 424.5
## Max. :4256.0 Max. :548.00 Max. :2165.0 Max. :1659.0
## CWalks League Division PutOuts Assists
## Min. : 1.0 A:139 E:129 Min. : 0.0 Min. : 0.0
## 1st Qu.: 71.0 N:124 W:134 1st Qu.: 113.5 1st Qu.: 8.0
## Median : 174.0 Median : 224.0 Median : 45.0
## Mean : 260.3 Mean : 290.7 Mean :118.8
## 3rd Qu.: 328.5 3rd Qu.: 322.5 3rd Qu.:192.0
## Max. :1566.0 Max. :1377.0 Max. :492.0
## Errors Salary NewLeague
## Min. : 0.000 Min. : 67.5 A:141
## 1st Qu.: 3.000 1st Qu.: 190.0 N:122
## Median : 7.000 Median : 425.0
## Mean : 8.593 Mean : 535.9
## 3rd Qu.:13.000 3rd Qu.: 750.0
## Max. :32.000 Max. :2460.0
Reparemos que há muitas variáveis altamente correlacionadas e outras sem correlação. Para nossos fins de regredir Salary
, realmente precisamos de um algoritmo de seleção de variáveis, ou redução delas. Vemos também variáveis sem uma corralação gráfica quando plotadas aos pares, com a variância do salário aumentando juntamente com o valor da abscissa, o que é particularmente danoso às hipóteses de uma regressão linear (a hipótese de homocedasticidade). Nesse contexto, é justificável a redução de dimensão pela criação de novas variáveis, que é o que os Mínimos Quadrados Parciais propõem.
ggcorr(dds %>%
select(!c('NewLeague', 'League', 'Division')),
label = T)
ggplotly(
ggplot(dds, aes(x=Salary)) +
geom_histogram(aes(y=..density..), bins = 30) +
geom_density() +
geom_vline(aes(xintercept = Salary %>%
mean(na.rm=T)),
linetype="dashed"))
ggplotly(
ggplot(dds, aes(x=Errors)) +
geom_histogram(aes(y=..density..), bins = 30) +
geom_density() +
geom_vline(aes(xintercept = Errors %>%
mean(na.rm=T)),
linetype="dashed"))
ggplotly(
ggplot(dds, aes(y=Salary, x=Runs)) +
geom_point(aes(color=Division))+
geom_density_2d())
ggplotly(
ggplot(dds, aes(y=Salary, x=Errors)) +
geom_point(aes(color=Division)) +
geom_density_2d(alpha=.5,
color='darkviolet'))
ggplotly(
ggplot(dds, aes(y=Salary, x=Hits)) +
geom_point(aes(color=Division)) +
geom_density_2d(alpha=.5,
color='darkviolet'))
ggplotly(
ggplot(dds, aes(y=Salary, x=Years)) +
geom_point(aes(color=Division))+
geom_density_2d(alpha=.5,
color='darkviolet'))
ggplotly(
ggplot(dds, aes(y=CHits, x=Hits)) +
geom_point(aes(color=Division)) )
Vamos fazer a primeira divisão entre treino e teste. Aplicaremos a reamostragem somente nos dados de treino, conforme Kuhn & Johnson (2019).
<- initial_split(dds)
slice_1 <- training(slice_1)
train_dds <- testing(slice_1) test_dds
Formaremos agora dois modelos: o primeiro Elastic Net conforme a equação acima; o segundo será usando Mínimos Quadrados Parciais, com o número fixo de sete novos regressores, criados a partir da combinação linear dos regressores dos originais. Para detalhes, veja Lavine e Rayens (2019). Repare que os parâmetros de afinação penalty
e mixture
estão para ser testados no cross validation, para escolhermos o melhor.
<- linear_reg(
algorit_elast penalty = tune::tune(),
mixture = tune::tune()) %>%
set_mode('regression') %>%
set_engine("glmnet")
algorit_elast
## Linear Regression Model Specification (regression)
##
## Main Arguments:
## penalty = tune::tune()
## mixture = tune::tune()
##
## Computational engine: glmnet
<- pls(num_comp = 7) %>%
algorit_pls set_mode("regression") %>%
set_engine("mixOmics")
algorit_pls
## PLS Model Specification (regression)
##
## Main Arguments:
## num_comp = 7
##
## Computational engine: mixOmics
Para preparar os dados para ser aplicado no modelo, aplicaremos o step_normalize(all_numeric_predictors())
para normalizar todas as variáveis numéricas e step_dummy(all_nominal_predictors())
para tranformar variáveis fator em dummies. A função bake()
“cozinha” e nos mostra como ficarão os dados.
<- recipe(Salary~.,
formula_geral data = train_dds) %>%
step_normalize(all_numeric_predictors()) %>%
step_dummy(all_nominal_predictors()) %>%
prep()
%>%
formula_geral bake(new_data=NULL) %>%
::paged_table(options = list(
rmarkdownrows.print = 10,
cols.print = 10))
Definiremos dois procedimentos por termos dois modelos diferentes. Nele, vamos alimentar a estrutura dos modelos com a fórmula transformada. Repare que, como não há nada para ser tunado na nossa regressão usando pls, já aplico a divisão final last_fit(slice_1)
. Retomaremos a wrkflw_pls
depois que afinarmos o Elastic Net.
<- workflow() %>%
wrkflw_elast add_model(algorit_elast) %>%
add_recipe(formula_geral)
<- workflow() %>%
wrkflw_pls add_model(algorit_pls) %>%
add_recipe(formula_geral)
<- wrkflw_pls %>%
wrkflw_pls last_fit(slice_1)
Definiremos o método de reamostragem para validação cruzada:
<- vfold_cv(train_dds,
kfold_geral v=10,
repeats = 2)
tune()
Montaremos pares de parâmetros a serem afinados. Veja que mixture
vai de 0 (Ridge) a 1 (Lasso):
<- wrkflw_elast %>%
grid_padr parameters() %>%
update(
penalty = penalty(range = c(.25, .75)),
mixture = mixture(range = c(0, 1))
%>%
) grid_regular(levels = 5)
%>%
grid_padr ::paged_table(options = list(
rmarkdownrows.print = 10,
cols.print = 2))
tune()
Apesar de montar dois parâmetros de métrica, usaremos o rmse
, que é mais sensível por pesar mais outliers.
<- wrkflw_elast %>%
afinç_cv_elast tune_grid(resamples = kfold_geral,
grid = grid_padr,
control = control_grid(save_pred = T),
metrics = metric_set(rmse, mae))
$.metrics wrkflw_pls
## [[1]]
## # A tibble: 2 × 4
## .metric .estimator .estimate .config
## <chr> <chr> <dbl> <chr>
## 1 rmse standard 314. Preprocessor1_Model1
## 2 rsq standard 0.537 Preprocessor1_Model1
Reparemos que interessante: o Ridge (mixture = 0
) sai de melhor e se torna o pior à medida que o peso dos preditores adicionais cresce.
%>% ggplot2::autoplot() afinç_cv_elast
Ainda sim, pelo show_best(n=1, 'rmse')
, vemos que algoritmo dos mínimos quadrados parciais performa melhor que a da regularização e penalização:
%>% show_best(n=1, 'rmse') afinç_cv_elast
## # A tibble: 1 × 8
## penalty mixture .metric .estimator mean n std_err .config
## <dbl> <dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 4.22 0.75 rmse standard 334. 20 24.6 Preprocessor1_Model19
%>% show_best(n=1, 'rmse') wrkflw_pls
## # A tibble: 1 × 7
## .workflow .metric .estimator mean n std_err .config
## <list> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 <workflow> rmse standard 314. 1 NA Preprocessor1_Model1
<- select_best(afinç_cv_elast, 'rmse') melhor_tune
Agora, extrairemos o melhor par de parâmetros do Elastic Net, montaremos o procedimento do workflow()
e aplicaremos na amostra de teste. Lembremos que já fazemos isso para o pls, já que não havia nada a ser afinado.
<- algorit_elast %>%
algorit_fnl_elast finalize_model(parameters = melhor_tune)
<- workflow() %>%
wrkflw_fnl_elast add_model(algorit_fnl_elast) %>%
add_recipe(formula_geral) %>%
last_fit(slice_1)
$.metrics wrkflw_fnl_elast
## [[1]]
## # A tibble: 2 × 4
## .metric .estimator .estimate .config
## <chr> <chr> <dbl> <chr>
## 1 rmse standard 314. Preprocessor1_Model1
## 2 rsq standard 0.537 Preprocessor1_Model1
$.metrics wrkflw_pls
## [[1]]
## # A tibble: 2 × 4
## .metric .estimator .estimate .config
## <chr> <chr> <dbl> <chr>
## 1 rmse standard 314. Preprocessor1_Model1
## 2 rsq standard 0.537 Preprocessor1_Model1
Percebemos, por $.metris
nos dois workflows, que o pls nos oferece a melhor predição baseado no rmse
e a maior correlação entre predição e valor verdadeiro, dado por rsq
. Agora, para representação, faremos os dois plots das duas técnicas, comparando estimados e reais:
<- wrkflw_fnl_elast %>%
gg_pred collect_predictions() %>%
ggplot(aes(x=.pred, y=Salary)) +
geom_point(alpha=.75) +
geom_abline(slope = 1,
intercept = 0,
color = 'palegreen4',
size =.8)
::ggMarginal(gg_pred,
ggExtratype = 'density',
margins = 'both',
colour = 'palegreen4',
fill = 'palegreen1')
Modelo Elastic Net
<- wrkflw_pls %>%
gg_pred_pls collect_predictions() %>%
ggplot(aes(x=.pred, y=Salary)) +
geom_point(alpha=.75) +
geom_abline(slope = 1,
intercept = 0,
color = 'salmon4',
size =.8)
::ggMarginal(gg_pred_pls,
ggExtratype = 'density',
margins = 'both',
colour = 'salmon4',
fill = 'salmon')
Modelo de Mínimos Quadrados Parciais