Capítulo 2 Regressão
library(tidyverse)
library(GGally)
library(broom)
A técnica chamada de regressão é usada para predizer o valor de uma variável Y (chamada de variável resposta ou dependente) baseado em uma ou mais variáveis X (variável explanatória ou independente). Se a regressão utiliza apenas uma variável explanatória, é chamada de regressão simples. O objetivo da regressão é representar a relação entre as variáveis resposta e explanatória por meio de uma equação matemática linear do tipo:
\(Y = \beta_1 + \beta_2X + \epsilon\)
onde \(\beta_1\) é a interceptação da reta com o eixo vertical e \(\beta2\) o coeficiente de inclinação associado à variável explanatória. Tais elementos são chamados coeficientes da regressão. O termo \(\epsilon\) representa o termo do erro, que é a parte de Y que a regressão é incapaz de explicar (por existir outras variáveis que explicariam Y mas que não foram incorporadas ao modelo).
Neste módulo, usaremos como exemplo o dataset do Kaggle de Bicicletas compartilhadas em Washington D.C., nos Estados Unidos. Baixe o arquivo zip do Kaggle e leia o README para entender o que o dataset representa e suas variáveis significam. Vamos importar apenas
df <- readr::read_csv("https://raw.githubusercontent.com/sillasgonzaga/curso_series_temporais/master/data/Bike-Sharing-Dataset/day.csv")
# dando olhada nos dados
# glimpse(df)
Seguindo o que aprendemos a partir da leitura do README, vamos fazer algumas transformações de colunas antes de proceder com a modelagem:
df_transf <- df %>%
# remover colunas irrelevantes
select(-c(instant, workingday)) %>%
# renomear algumas colunas
rename(
estacao = season,
total = cnt,
year = yr,
month = mnth
) %>%
# mudar weekday, que começa a contar do zero
mutate(weekday = weekday + 1) %>%
# transformar a variavel de feriado para texto
mutate(holiday = as.character(holiday)) %>%
# mudar os valores de algumas variaveis
mutate(
# substituir o codigo do ano pelo ano real
year = lubridate::year(dteday),
# adicionar um leading zero no mês
month = str_pad(month, width = 2, side = "left", pad = "0"),
# converter weathersit para variavel do tipo factor
weathersit = factor(weathersit,
levels = 1:4,
labels = c("muito bom", "bom", "ruim", "muito ruim")),
# converter dia da semana para variavel do tipo factor
weekday = factor(weekday,
levels = 1:7,
labels = c("Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab")),
# fazer o mesmo para estacao
estacao = factor(estacao,
levels = 1:4,
labels = c("Primavera", "Verao", "Outono", "Inverno")),
# converter colunas numericas para escala normal (não-normalizada)
temp = temp * 41,
atemp = atemp * 50,
hum = hum * 100,
windspeed = windspeed * 67
)
Estamos interessados em entender o que influencia a demanda de bicicletas alugadas por dia.
2.1 Análise exploratória
O primeiro passo para entendermos nossa variável de estudo é criar um gráfico da série:
df_transf %>%
ggplot(aes(x = dteday, y = total)) +
geom_line() +
# adicionar curva de tendencia
geom_smooth(se = FALSE) +
theme_bw() +
# quebrar eixo x em 1 mes
scale_x_date(date_breaks = "1 month",
date_labels = "%m/%Y",
minor_breaks = NULL) +
# inverter eixos
theme(axis.text.x = element_text(angle = 90))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
Apenas com o gráfico acima, podemos aprender uma série de insights interessantes:
- Parece haver múltiplas sazonalidades que afetam a demanda por bicicletas alugadas: dia da semana, mês, ano e estação do ano.
- Não existe um componente de tendência linear, pois as altas e quedas são mais em função das sazonalidades descritas acima.
Exercícios:
- Como você faria para representar a estação do ano no gráfico acima? Teste duas abordagens: acrescente linhas verticas tracejadas marcando a transição das estações ou pinte a linha (usando aes
) de acordo com a estação.
- Explore com mais detalhes a distribuição da variável total
em função de:
- Dia da semana & Feriado
- Dia da semana & Condição do tempo
- Mês & Estação do ano
- Mês & Ano
2.2 Correlação
Correlação é um indicador estatístico que mede o nível de dependência linear entre duas variáveis. Está definida no intervalo \([-1, +1]\). Se a correlação é negativa, indica que as variáveis são inversamente proporcinais: quando uma aumenta, a outra diminui. Se é positiva, indica que as variáveis são diretamente proporcionais.
Medir a correlação no R é muito simples:
# Usando a função cor
cor(df_transf$total, df_transf$temp)
## [1] 0.627494
cor(df_transf$temp, df_transf$atemp)
## [1] 0.9917016
Como poderia se esperar, as variáveis temp
e atemp
são praticamente a mesma, apresentando uma correlação quase igual a 1. Isso, em regressão, é um problema chamado multicolinearidade. Por isso, é necessário remover uma delas:
df_transf <- df_transf %>%
select(-atemp)
No entanto, é possível analisar todos os pares possíveis entre as variáveis de uma matriz numérica:
df_transf %>%
select_if(is.numeric) %>%
select(-year) %>%
cor()
## temp hum windspeed casual registered
## temp 1.0000000 0.12696294 -0.1579441 0.54328466 0.5400120
## hum 0.1269629 1.00000000 -0.2484891 -0.07700788 -0.0910886
## windspeed -0.1579441 -0.24848910 1.0000000 -0.16761335 -0.2174490
## casual 0.5432847 -0.07700788 -0.1676133 1.00000000 0.3952825
## registered 0.5400120 -0.09108860 -0.2174490 0.39528245 1.0000000
## total 0.6274940 -0.10065856 -0.2345450 0.67280443 0.9455169
## total
## temp 0.6274940
## hum -0.1006586
## windspeed -0.2345450
## casual 0.6728044
## registered 0.9455169
## total 1.0000000
Um incremento ainda melhor é usar o pacote GGally
para plotar uma matriz de correlação:
df_transf %>%
select_if(is.numeric) %>%
select(-c(year, casual, registered)) %>%
GGally::ggpairs(progress = FALSE)
Percebe-se pela matriz de correlação (e principalmente pelo gráfico) que talvez só valeria a pena usar como variáveis explanatórias do nosso objeto de estudo a temperatura do dia.
2.3 Modelagem por regressão simples
No R, é bem simples ajustar um modelo de regressão. Usando a variável temp
como explanatória e total
como resposta, um modelo é construído da seguinte maneira:
modelo.simples <- lm(total ~ temp, data = df_transf)
summary(modelo.simples)
##
## Call:
## lm(formula = total ~ temp, data = df_transf)
##
## Residuals:
## Min 1Q Median 3Q Max
## -4615.3 -1134.9 -104.4 1044.3 3737.8
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1214.642 161.164 7.537 1.43e-13 ***
## temp 161.969 7.444 21.759 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1509 on 729 degrees of freedom
## Multiple R-squared: 0.3937, Adjusted R-squared: 0.3929
## F-statistic: 473.5 on 1 and 729 DF, p-value: < 2.2e-16
Com o modelo criado, é possível descrever a relação entre consumo
e n_carteiras
matematicamente por meio da seguinte equação:
\(total = 1214.642 + 161.969 \times temperatura\)
Vamos deixar para analisar os diagnósticos da regressão no próximo item:
2.4 Regressão multivariada
Suponha também que você deseja incorporar as outras variáveis que detectamos que são importantes para modelar a variável da quantidade de bikes alugadas:
# sintaxe para incluir todas as variaveis como regressoras menos uma (dteday)
modelo.multiplo <- lm(total ~ . - dteday - casual - registered, data = df_transf)
summary(modelo.multiplo)
##
## Call:
## lm(formula = total ~ . - dteday - casual - registered, data = df_transf)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3960.9 -350.9 74.1 456.0 2919.9
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -4.057e+06 1.171e+05 -34.634 < 2e-16 ***
## estacaoVerao 8.893e+02 1.795e+02 4.954 9.12e-07 ***
## estacaoOutono 8.322e+02 2.132e+02 3.903 0.000104 ***
## estacaoInverno 1.579e+03 1.810e+02 8.722 < 2e-16 ***
## year 2.018e+03 5.823e+01 34.660 < 2e-16 ***
## month02 1.369e+02 1.437e+02 0.952 0.341396
## month03 5.451e+02 1.655e+02 3.294 0.001036 **
## month04 4.565e+02 2.476e+02 1.844 0.065667 .
## month05 7.235e+02 2.675e+02 2.704 0.007010 **
## month06 4.906e+02 2.818e+02 1.741 0.082133 .
## month07 8.404e+00 3.134e+02 0.027 0.978613
## month08 4.049e+02 3.015e+02 1.343 0.179700
## month09 9.839e+02 2.647e+02 3.717 0.000217 ***
## month10 5.209e+02 2.416e+02 2.156 0.031432 *
## month11 -1.114e+02 2.308e+02 -0.482 0.629621
## month12 -8.439e+01 1.822e+02 -0.463 0.643439
## holiday1 -6.036e+02 1.801e+02 -3.352 0.000845 ***
## weekdaySeg 2.149e+02 1.095e+02 1.962 0.050133 .
## weekdayTer 3.091e+02 1.072e+02 2.884 0.004041 **
## weekdayQua 3.774e+02 1.075e+02 3.512 0.000473 ***
## weekdayQui 3.852e+02 1.076e+02 3.581 0.000366 ***
## weekdaySex 4.286e+02 1.073e+02 3.996 7.12e-05 ***
## weekdaySab 4.387e+02 1.066e+02 4.116 4.32e-05 ***
## weathersitbom -4.652e+02 7.708e+01 -6.035 2.57e-09 ***
## weathersitruim -1.981e+03 1.967e+02 -10.075 < 2e-16 ***
## temp 1.094e+02 1.004e+01 10.896 < 2e-16 ***
## hum -1.518e+01 2.922e+00 -5.196 2.68e-07 ***
## windspeed -4.366e+01 6.062e+00 -7.202 1.53e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 769.5 on 703 degrees of freedom
## Multiple R-squared: 0.848, Adjusted R-squared: 0.8422
## F-statistic: 145.3 on 27 and 703 DF, p-value: < 2.2e-16
Valores altos de impostos aparentam estar associados com valores baixos de consumo.
Para adicionar uma nova variável ao modelo, fazemos:
# Usando o pacote broom para formatar o output dos modelos de regressao
# concatenando os dois modelos em um dataframe so
# metricas dos regressores
modelo.simples %>% tidy()
## # A tibble: 2 x 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 1215. 161. 7.54 1.43e-13
## 2 temp 162. 7.44 21.8 2.81e-81
modelo.multiplo %>% tidy()
## # A tibble: 28 x 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) -4056781. 117133. -34.6 3.97e-154
## 2 estacaoVerao 889. 180. 4.95 9.12e- 7
## 3 estacaoOutono 832. 213. 3.90 1.04e- 4
## 4 estacaoInverno 1579. 181. 8.72 1.97e- 17
## 5 year 2018. 58.2 34.7 2.85e-154
## 6 month02 137. 144. 0.952 3.41e- 1
## 7 month03 545. 165. 3.29 1.04e- 3
## 8 month04 456. 248. 1.84 6.57e- 2
## 9 month05 724. 268. 2.70 7.01e- 3
## 10 month06 491. 282. 1.74 8.21e- 2
## # … with 18 more rows
# metricas do modelo
modelo.simples %>% glance()
## # A tibble: 1 x 11
## r.squared adj.r.squared sigma statistic p.value df logLik AIC
## <dbl> <dbl> <dbl> <dbl> <dbl> <int> <dbl> <dbl>
## 1 0.394 0.393 1509. 473. 2.81e-81 2 -6387. 12780.
## # … with 3 more variables: BIC <dbl>, deviance <dbl>, df.residual <int>
modelo.multiplo %>% glance()
## # A tibble: 1 x 11
## r.squared adj.r.squared sigma statistic p.value df logLik AIC
## <dbl> <dbl> <dbl> <dbl> <dbl> <int> <dbl> <dbl>
## 1 0.848 0.842 770. 145. 1.49e-266 28 -5881. 11820.
## # … with 3 more variables: BIC <dbl>, deviance <dbl>, df.residual <int>
2.5 Diagnóstico da regressão
A diferença entre os valores observados (chamamos de \(y\)) e os ajustados ou previstos (\(\hat{y}\)) é chamada de erro ou resíduo, definido como:
\(e_t = y_t - \hat{y_t}\)
O resíduo representa o componente imprevisível da observação \(t\). Algumas propriedades (ou pressupostos) dos resíduos devem ser observados na análise de um modelo de regressão:
- A média dos resíduos é igual a zero;
- Não existe correlação entre os resíduos e os valores ajustados;
- Não existe autocorrelação nos resíduos.
2.5.1 Histograma dos resíduos
A função forecast::checkresiduals()
é muito útil para criar um gráfico de diagnóstico dos resíduos de um modelo de regressão:
forecast::checkresiduals(modelo.simples)
##
## Breusch-Godfrey test for serial correlation of order up to 10
##
## data: Residuals
## LM test = 471.12, df = 10, p-value < 2.2e-16
forecast::checkresiduals(modelo.multiplo)
##
## Breusch-Godfrey test for serial correlation of order up to 31
##
## data: Residuals
## LM test = 148.71, df = 31, p-value < 2.2e-16
Discussão:
- O que se observa nos 3 gráficos referentes a cada modelo? Dá para analisar qual é melhor?
- Dá para melhorar o modelo de regressão multivariada com a informação de algum gráfico?
- Existe algum outlier? O que podemos fazer para melhorar o modelo com essa informação?
options(scipen = 999)
rbind(forecast::CV(modelo.simples), forecast::CV(modelo.multiplo))
## CV AIC AICc BIC AdjR2
## [1,] 2283547.6 10705.048 10705.081 10718.831 0.3929171
## [2,] 623026.6 9745.553 9748.035 9878.791 0.8422094
?forecast::CV
2.5.2 Hipótese nula da regressão
A presença de um valor-p indica que existe uma hipótese nula sendo testada. Na regressão linear, a hipótese nula é a de que os coeficientes das variáveis explanatórias são iguais a zero. A hipótese alternativa é a de que os coeficientes não são iguais a zero, ou seja, existe uma relação matemático entre as variáveis do modelo.
2.5.3 valor-p
Nós podemos considerar um modelo linear estatisticamente significante apenas se os valores-p, tanto dos coeficientes como do modelo, são menores que um nível de significância pré-determinado, que idealmente é 0,05.
2.5.4 R-quadrado e R-quadrado ajustado
R-quadrado é a proporção da variação da variável resposta que é explicada pelo modelo. Quanto maior, melhor o modelo, supostamente.
Se continuarmos adicionando variáveis ao modelo de regressão, o R-quadrado apenas tende a crescer, intuitivamente. Isso acontecerá mesmo que a variável explanatória adicionada não seja significante. Para evitar esse problema que tornaria a comparação entre modelos praticamente inviável, o R-quadrado ajustado “penaliza” o valor do R-quadrado pelo número de variáveis adicionadas. Semelhantemente ao R-quadrado, quanto maior, melhor.
2.5.5 Análise dos resíduos
Um indicador visual da qualidade de um modelo é a distribuição dos modelos: um bom modelo apresentará resídos que seguem uma distribuição normal com média 0.
Um modelo de regressão pressupõe que seus resíduos (subtração entre o valor real e o ajustado) seguem uma distribuição normal e não possuem nenhum tipo de relação matemática com os regressores do modelo (ou mesmo com variáveis independentes não usadas no modelo).
forecast::checkresiduals(modelo.multiplo)
##
## Breusch-Godfrey test for serial correlation of order up to 31
##
## data: Residuals
## LM test = 148.71, df = 31, p-value < 0.00000000000000022
2.6 Regressão como modelo preditivo
Um dos objetivos da regressão, além de descrever matematicamente a relação entre duas ou mais variáveis, é prever o valor da variável dependente baseado em novos valores da variável independente. Não é possível afirmar que um modelo apresentará um bom desempenho preditivo analisando apenas as métricas da regressão do tópico anterior. É necessário testar o modelo em dados que ele nunca viu.
A prática comum em Data Science é de separar o conjunto de dados que se tem em mãos em dois conjuntos: o de treino, que será usado para construir o modelo, e o de teste, que será usado como input do modelo para avaliar sua acurácia.
Após obter as previsões, deve-se usar uma ou mais métricas de erro (ver capítulo posterior) para avaliar a precisão do modelo.
indice_teste <- tail(1:nrow(df_transf), 60)
treino <- df_transf[-indice_teste, ] # model training data
teste <- df_transf[indice_teste, ] # test data
# construindo os dois modelos, mantendo o teste de fora
modelo.simples <- lm(total ~ temp, data = treino)
modelo.multiplo <- lm(total ~ . - dteday - casual - registered, data = treino)
# calcular previsao baseado no dataframe de teste
prev.simples <- predict(modelo.simples, teste)
prev.mult <- predict(modelo.multiplo, teste)
# uma das metricas é correlação entre previsto e real:
real <- teste$total
# outra metrica é o MAPE
ape <- function(yreal, yprev) {
abs((yreal - yprev)/yreal)
}
mean(ape(yreal = real, yprev = prev.simples))
## [1] 0.4816825
mean(ape(yreal = real, yprev = prev.mult))
## [1] 0.3890409
Os dois modelos apresentam resultados semelhantes de erro. Portanto, pelo menos para este teste, não houve um aumento significativo de acurácia no modelo ao incorporar a variável imposto
como explanatória.
2.7 Referências
- Pressupostos sobre regressão linear;
- Datasets para você praticar regressão linear.
Exercícios:
- Importe este dataset de consumo de petróleo para o R.
- Qual a variável resposta?
- Quais variáveis explanatórias incluir no modelo?
- Quais gráficos para analisar as variáveis e os modelos?