Este trabalho tem como objetivo fazer uma análise exploratoria dos dados coletados no kaggle e criar um modelo que possa prever o valor do aluguel de casas nas regiões de São Paulo, Rio de Janeiro, Porto Alegre, Campinas e Belo Horizonte.
Os dados coletados são referentes ao dataset disponibilizado no kaggle por Rubens Junior (https://www.kaggle.com/rubenssjr/brasilian-houses-to-rent), possuem informações retiradas através de Webcrawler em diversos sites com ofertas de alugueis de imoveis. Os dados contém 10962 observações e 13 vaariaveis. Possuem as seguintes colunas:
city= Cidade; area= Tamanho da casa em m2; rooms= Número de quartos; Bathroom= Número de banheiros; Parking spaces= Número de vagas em estacionamento; Floor= Número de andar; Animal= Se aceita animais ou não; Furniture= Se o local é mobiliado ou não; Hoa= Valor do condominio; rent amount (R\()= Valor do aluguel; property tax (R\))= IPTU da casa; fire insurance (R$)= Seguro de incendio da casa; Total= Total pago ao mês pela casa;
#install.packages("readr")
#install.packages("plotly")
library(readr)
library(visdat)
library(dplyr)
library(ggplot2)
library(plotly)
library(dplyr)
library(corrplot)
library(randomForest)
library(caret)
#install.packages("faraway")
library(faraway)
#install.packages("lmtest")
library(lmtest)
#install.packages("MVar.pt")
library(MVar.pt)
#install.packages(nortest)
library(nortest)
#install.packages("fpp")
library(fpp)
#install.packages("leaps")
library(leaps)
df<- read_csv("houses_to_rent_v2.csv")
##
## -- Column specification -------------------
## cols(
## city = col_character(),
## area = col_double(),
## rooms = col_double(),
## bathroom = col_double(),
## `parking spaces` = col_double(),
## floor = col_character(),
## animal = col_character(),
## furniture = col_character(),
## `hoa (R$)` = col_double(),
## `rent amount (R$)` = col_double(),
## `property tax (R$)` = col_double(),
## `fire insurance (R$)` = col_double(),
## `total (R$)` = col_double()
## )
head(df)
## # A tibble: 6 x 13
## city area rooms bathroom `parking spaces` floor animal furniture `hoa (R$)`
## <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <chr> <dbl>
## 1 São ~ 70 2 1 1 7 acept furnished 2065
## 2 São ~ 320 4 4 0 20 acept not furn~ 1200
## 3 Port~ 80 1 1 1 6 acept not furn~ 1000
## 4 Port~ 51 2 1 0 2 acept not furn~ 270
## 5 São ~ 25 1 1 0 1 not a~ not furn~ 0
## 6 São ~ 376 3 3 7 - acept not furn~ 0
## # ... with 4 more variables: `rent amount (R$)` <dbl>, `property tax
## # (R$)` <dbl>, `fire insurance (R$)` <dbl>, `total (R$)` <dbl>
glimpse(df)
## Rows: 10,692
## Columns: 13
## $ city <chr> "São Paulo", "São Paulo", "Porto Alegre", "Po...
## $ area <dbl> 70, 320, 80, 51, 25, 376, 72, 213, 152, 35, 2...
## $ rooms <dbl> 2, 4, 1, 2, 1, 3, 2, 4, 2, 1, 1, 1, 1, 1, 2, ...
## $ bathroom <dbl> 1, 4, 1, 1, 1, 3, 1, 4, 2, 1, 1, 1, 1, 1, 2, ...
## $ `parking spaces` <dbl> 1, 0, 1, 0, 0, 7, 0, 4, 1, 0, 0, 1, 0, 1, 2, ...
## $ floor <chr> "7", "20", "6", "2", "1", "-", "7", "4", "3",...
## $ animal <chr> "acept", "acept", "acept", "acept", "not acep...
## $ furniture <chr> "furnished", "not furnished", "not furnished"...
## $ `hoa (R$)` <dbl> 2065, 1200, 1000, 270, 0, 0, 740, 2254, 1000,...
## $ `rent amount (R$)` <dbl> 3300, 4960, 2800, 1112, 800, 8000, 1900, 3223...
## $ `property tax (R$)` <dbl> 211, 1750, 0, 22, 25, 834, 85, 1735, 250, 35,...
## $ `fire insurance (R$)` <dbl> 42, 63, 41, 17, 11, 121, 25, 41, 191, 30, 27,...
## $ `total (R$)` <dbl> 5618, 7973, 3841, 1421, 836, 8955, 2750, 7253...
Com o objetivo de deixar mais simples a manipulação dos dados e o entendimento, decidimos alterar o nome das variaveis.
names(df)= c("cidade","area","quartos","banheiros","n_estacionamento", "andar","animal","Furniture","condominio","valor_aluguel","IPTU","taxa_incendio","total_pago")
head(df)
## # A tibble: 6 x 13
## cidade area quartos banheiros n_estacionamento andar animal Furniture
## <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <chr>
## 1 São P~ 70 2 1 1 7 acept furnished
## 2 São P~ 320 4 4 0 20 acept not furn~
## 3 Porto~ 80 1 1 1 6 acept not furn~
## 4 Porto~ 51 2 1 0 2 acept not furn~
## 5 São P~ 25 1 1 0 1 not a~ not furn~
## 6 São P~ 376 3 3 7 - acept not furn~
## # ... with 5 more variables: condominio <dbl>, valor_aluguel <dbl>, IPTU <dbl>,
## # taxa_incendio <dbl>, total_pago <dbl>
Inicialmente iremos avaliar se o dataset possuem algum valor faltante.Abaixo é possível perceber que não possuimos nenhum dado faltante, não precisando fazer qualquer tipo de tratamento.
colSums(is.na(df))
## cidade area quartos banheiros
## 0 0 0 0
## n_estacionamento andar animal Furniture
## 0 0 0 0
## condominio valor_aluguel IPTU taxa_incendio
## 0 0 0 0
## total_pago
## 0
Para começarmos a fazer qualquer tipo de análise, precisamos antes de tudo validar se as variáveis do nosso dataset estão formatadas com tipo de dados certo. Para ficar visualmente mais atrativo, decidimos utilizar o pacote visdat.
vis_dat(df)
Precisamos mudar os tipos das variáveis cidade, animal e furniture para o tipo fator. Embora realmente esses dados sejam strigs, elas representam uma variável de classificação.
df$cidade= as.factor(df$cidade)
df$animal= as.factor(df$animal)
df$Furniture= as.factor(df$Furniture)
df$banheiros=as.integer(df$banheiros)
df
## # A tibble: 10,692 x 13
## cidade area quartos banheiros n_estacionamento andar animal Furniture
## <fct> <dbl> <dbl> <int> <dbl> <chr> <fct> <fct>
## 1 São P~ 70 2 1 1 7 acept furnished
## 2 São P~ 320 4 4 0 20 acept not furn~
## 3 Porto~ 80 1 1 1 6 acept not furn~
## 4 Porto~ 51 2 1 0 2 acept not furn~
## 5 São P~ 25 1 1 0 1 not a~ not furn~
## 6 São P~ 376 3 3 7 - acept not furn~
## 7 Rio d~ 72 2 1 0 7 acept not furn~
## 8 São P~ 213 4 4 4 4 acept not furn~
## 9 São P~ 152 2 2 1 3 acept furnished
## 10 Rio d~ 35 1 1 0 2 acept furnished
## # ... with 10,682 more rows, and 5 more variables: condominio <dbl>,
## # valor_aluguel <dbl>, IPTU <dbl>, taxa_incendio <dbl>, total_pago <dbl>
vis_dat(df)
A primeira pergunta que faremos aos dados é como as observações estão distribuidas em relação as cidades, quais cidades possuem mais observações ?
g=ggplot(data=df, aes(x=cidade)) + geom_bar(fill="lightblue",)+ coord_flip()+
theme_classic() + labs(title = "Número de casas para alugar por cidades") + ylab("numero de casas")
ggplotly(g)
O gráfico de barras acima mostra que a grande maioria dos número de alugueis observados foi da cidade de São Paulo, essa má distribuição dos dados talvez possa fazer o modelo criar um pequeno viés quando a cidade é são Paulo.
Para proteger esta análise de possíveis valores extremos decidimos utilizar algumas medidas de localização e variabilidades que sejam menos influenciadas por valores extremos, tal como a média aparada e o desvio absoluto mediano (MAD).
#Media aparada
aggregate(df[,"valor_aluguel"], list(local=df$cidade),trim=0.1,mean)
## local valor_aluguel
## 1 Belo Horizonte 2956.688
## 2 Campinas 1862.445
## 3 Porto Alegre 1879.297
## 4 Rio de Janeiro 2671.480
## 5 São Paulo 4055.008
#MAD
aggregate(df[,"valor_aluguel"], list(local=df$cidade), mad)
## local valor_aluguel
## 1 Belo Horizonte 1779.120
## 2 Campinas 978.516
## 3 Porto Alegre 963.690
## 4 Rio de Janeiro 1497.426
## 5 São Paulo 2490.768
aggregate(df[,"valor_aluguel"], list(local=df$cidade), max)
## local valor_aluguel
## 1 Belo Horizonte 15000
## 2 Campinas 15000
## 3 Porto Alegre 19000
## 4 Rio de Janeiro 15000
## 5 São Paulo 45000
aggregate(df[,"valor_aluguel"], list(local=df$cidade), min)
## local valor_aluguel
## 1 Belo Horizonte 450
## 2 Campinas 500
## 3 Porto Alegre 500
## 4 Rio de Janeiro 500
## 5 São Paulo 500
Aparando a média em 10% a cidade de São Paulo possuí em média os alugueis mais caros entre os locais analisados. O desvio absoluto mediano mostra que para todas as cidades existe uma grande variabilidade nos valores de alugueis, temos valores de R$ 500,00 a R$ 45000,00 em São Paulo por exemplo, mostrando que os dados estão bem distribuidos.
Para ter um entedimento melhor sobre quais as variáveis podem influênciar as variações do valor do aluguel, abaixo segue uma gráfico que mostra a correlação entre as variaveis.
colnumericas=sapply(df, is.numeric)
correlacao= cor(df[,colnumericas])
corrplot(correlacao, method = "color",addCoef.col =T)
É possivel notar uma correlação positiva média a forte entre as variaveis taxa de incendio, número de quartos, número de banheiros e número de vagas de estacionamento com a váriavel valor do aluguel. (Outra correlação muito forte é com o total pago, mas essa variavel não ajudará nada nosso modelo, pois seu valor depende do preço do aluguel) Excluiremos a variável total_pago pois ela não trás nenhuma informação útil para o modelo
df$total_pago=NULL
Devido a grande quantidade de variáveis para analisar, selecionamos apenas as features que tiveram uma alta ou média correlação (Taxa de incêndio, numero de banheiros, numero de estacionamento e numero de quartos) para enteder como elas se comportam em relação ao valor do aluguel.
Para avaliar essa distribuição tendo em vista que as 3 variáveis (quartos, Banheiros e Estacionamentos) são do tipo fator, a melhor forma de analisar sua relação com o valor do aluguel é com um boxplot. Abaixo segue os gráficos gerados:
lista=c("banheiros","quartos","n_estacionamento")
legenda=c("Valor do aluguel pela quantidade de banheiros",
"Valor do alguel pela quantidade de quartos",
"Valor do aluguel pela quantidade de n_estacionamento")
plot.boxes<-function(X,legenda){
ggplot(df,aes_string(x=X, y="valor_aluguel",group=X))+
geom_boxplot() + labs(title = legenda)+
theme_classic()
}
Map(plot.boxes,lista,legenda)
## $banheiros
##
## $quartos
##
## $n_estacionamento
Com o Boxplot fica claro o crescimento da mediana a medida que o número de quartos,estacionamento e banheiros aumentam. Além disso, começa-se a enchegar os valores extremos no valor do aluguel ,esses outlines eram esperados pois dependendo da localização de uma casa nas cidades os valores serão totalmente diferentes mesmo que elas possuam a mesma quantidade de quartos, banheiros ou estacionamento.
Utilizando um gráfico de violino é possível entender como a distribuição dos dados do valor do aluguel se comportam a medida que temos mudanças nas variaveis categóricas. É possível identificar que para todas as variáveis a medida que seus números são maiores(quartos, Banheiros,Vagas de estacionamento) mais esparço e menos entendível fica a distribuição dos dados (muito provavelmente devido a falta de dados extremos).
lista=c("banheiros","quartos","n_estacionamento")
legenda=c("Valor do aluguel pela quantidade de banheiros",
"Valor do alguel pela quantidade de quartos",
"Valor do aluguel pela quantidade de n_estacionamento")
plot.boxes<-function(X,legenda){
ggplot(df,aes_string(x=X, y="valor_aluguel",group=X))+
geom_violin() + labs(title = legenda)
}
Map(plot.boxes,lista,legenda)
## $banheiros
##
## $quartos
##
## $n_estacionamento
Abaixo segue um gráfico de dispersão entre a variavel valor aluguel e taxa de incêndio, devido a grande quantidade de pontos, decidimos utilizar uma visualização de compartimentação Hexagonal.
#Geral
ggplot(df, aes(x=taxa_incendio,y=valor_aluguel)) + theme_bw() + stat_binhex(colour="blue") + scale_fill_gradient(low="white", high="black")+
labs(x="Taxa de incêndio",y="Valor do aluguel", title = "Valor do Aluguel vs Taxa de incêncio (Geral)")
Fica claro que a relação do valor do aluguel e a taxa de incêndio tem um comportamento linear onde a medida que a taxa de incêndio aumenta o valor do aluguel tende a aumentar também.
Abaixo temos uma análise estratificada por cidade, é possível identificar que para todas as cidades do dataset possuimos um comportamento linear com uma correlação forte e com padrões semelhantes.
#Por cidade
ggplot(df, aes(x=taxa_incendio,y=valor_aluguel)) + theme_bw() + stat_binhex(colour="blue") + scale_fill_gradient(low="white", high="black")+
labs(x="Taxa de incêndio",y="Valor do aluguel", title = "Valor do Aluguel vs Taxa de incêncio (Por cidade)") + facet_wrap(df$cidade)
Serão feitos dois testes de hipóteses para entender se essas questões tem influência no valor do aluguel. Para tal, os dados serão divididos em duas classes, para alugueis abaixo de 2500 mil e acima deste valor, esta separação ajudará a entender se a hipótese é significativa ou não para casas mais baratas e mais caras. Abaixo temos o valor médio aparado de alugueis para os dois grupos:
#Criando a nova variavel categorica
df$tipo="Barata"
df$tipo[df$valor_aluguel>2500]="Caro"
#Calculando a média para os dois grupos
aggregate(df[,"valor_aluguel"], list(local=df$tipo),trim=0.1,mean)
## local valor_aluguel
## 1 Barata 1538.130
## 2 Caro 5533.138
Com base nessa divisão as casas baratas tem um valor médio de R$ 1538,130 e as casas mais caras tem um valor médio de R$ 5533,138.
Para ficar mais fácil de trabalhar substituiresmos os valores da variável Furniture para 1 caso seja mobiliada e 0 caso não seja mobiliada.
muda = function(x){
if (x=="furnished"){
return (1)
}else{
return(0)
}
}
df$Furniture= sapply(df$Furniture, muda)
Analisando os valores abaixo é possível notar que em média quando uma casa é mobiliada fica R$ 313,835 mais caro, resta analisarmos se essa diferença é estatisticamente significativa ou se pode ocorrer pelo acaso. Para isso será utilizado o teste de permutação aleatória, este teste é útil pois não precisamos nos preocupar com a distribuição dos dados, tamanho da amostra e se os dados são igualmente distribuidos.
casas_baratas=df[df$tipo=="Barata",]
media_mobiliado=mean(casas_baratas$valor_aluguel[casas_baratas$Furniture==1], trim = 0.1)
media_n_mobiliado=mean(casas_baratas$valor_aluguel[casas_baratas$Furniture==0], trim = 0.1)
c(media_n_mobiliado, media_mobiliado)
## [1] 1486.600 1800.431
Media_diff= media_mobiliado- media_n_mobiliado
Visualizando os dados em Boxplot é possível perceber uma certa significancia pois o terceiro quartil das casas não mobiliadas são práticamente o primeiro quartil das casas mobiliadas, mostrando um aumento nos preços.
ggplot(data=casas_baratas, aes_string(x=casas_baratas$Furniture, y=casas_baratas$valor_aluguel, group=casas_baratas$Furniture)) + geom_boxplot() + theme_classic() + labs(x="Mobiliado ou não", y="Valor do aluguel", title = "Distribuição do aluguel de casas baratas pelo fator de ser mobiliado")
Abaixo segue o código utilizado para o teste de permutação, onde foi considerado a hipótese Ho como verdadeira (Não há diferênça entre as médias). Com base nisso retiramos 1000 vezes de forma aleatoria dois grupos (n1 e n2) e calculamos a média para ver a distribuição da diferença desses valores e comparar com a diferença calculada sem a permutação para encontrar as chances de o acaso ser responsável por essa variação no valor do aluguel.
#tamanho da amostra de mobiliados (819)
length(casas_baratas$valor_aluguel[casas_baratas$Furniture==1])
## [1] 819
#Tamanho da amostra de não mobiliados (4350)
length(casas_baratas$valor_aluguel[casas_baratas$Furniture==0])
## [1] 4350
#Tamanho total de 5169
#Criando funçãod e permutação considerado hipotese h0
perm_fun = function(x,n1,n2){
n= n1 + n2
idx_b= sample(1:n, n1)
idx_a= setdiff(1:n, idx_b)
mean_diff= mean(x[idx_b], trim = 0.1)- mean(x[idx_a], trim = 0.1)
return (mean_diff)
}
perm_diffs= rep(0,1000)
for(i in 1:1000)
perm_diffs[i]= perm_fun(casas_baratas$valor_aluguel, 819,4350)
hist(perm_diffs, xlim=c(-100,500),nclass = 80)
abline(v= (Media_diff))
#valor p
p=mean(perm_diffs>Media_diff)
p
## [1] 0
O histograma acima mostra a frequência da diferença entre as médias encontradas geradas de forma aleatória e a linha constante representa o valor da diferença das médias real que é de R$ 313.815.É possível perceber que é bem improvável que o acaso seja responsável por essa diferença. O p-value associado é 0, logo rejeitamos a hipótese nula de que a diferença pode ser explicada pelo acaso.
Para analisar a diferença do valor do aluguel em casas caras foi utilizado os mesmos procedimentos descritos acima, então para não ficar repetitivo só colocarei o código utilizado.
casas_cara=df[df$tipo=="Caro",]
media_mobiliado=mean(casas_cara$valor_aluguel[casas_cara$Furniture==1], trim = 0.1)
media_n_mobiliado=mean(casas_cara$valor_aluguel[casas_cara$Furniture==0], trim = 0.1)
c(media_n_mobiliado, media_mobiliado)
## [1] 5416.686 5780.968
Media_diff= media_mobiliado- media_n_mobiliado
ggplot(data=casas_cara, aes_string(x=casas_cara$Furniture, y=casas_cara$valor_aluguel, group=casas_cara$Furniture)) + geom_boxplot() + theme_classic() + labs(x="Mobiliado ou não", y="Valor do aluguel", title = "Distribuição do aluguel de casas Caras pelo fator de ser mobiliado")
#tamanho da amostra de mobiliados (1787)
length(casas_cara$valor_aluguel[casas_cara$Furniture==1])
## [1] 1787
#Tamanho da amostra de não mobliados (3736)
length(casas_cara$valor_aluguel[casas_cara$Furniture==0])
## [1] 3736
#Tamanho total de 5523
#Criando funçãod e permutação considerado hipotese h0
perm_fun = function(x,n1,n2){
n= n1 + n2
idx_b= sample(1:n, n1)
idx_a= setdiff(1:n, idx_b)
mean_diff= mean(x[idx_b], trim = 0.1)- mean(x[idx_a], trim = 0.1)
return (mean_diff)
}
perm_diffs= rep(0,1000)
for(i in 1:1000)
perm_diffs[i]= perm_fun(casas_cara$valor_aluguel, 1787,3736)
hist(perm_diffs, xlim=c(-100,500),nclass = 80)
abline(v= (Media_diff))
#valor p
p=mean(perm_diffs>Media_diff)
p
## [1] 0.001
O teste de permutação aleatória gerou um valor de p de 0.001, logo rejeitamos a hipótese nula de que a diferença entre as médias pode ter ocorrido pelo acaso.
Para facilitar as análises feitas mudaremos o valor da variável animal, onde caso se aceite animais o valor será 1, caso não aceite o valor será 0, abaixo utilizamos uma função da familia Apply do R para converter os valores categóricos em 0 e 1.
muda = function(x){
if (x=="acept"){
return (1)
}else{
return(0)
}
}
df$animal= sapply(df$animal, muda)
Analisando a média aparada para as casas que aceitam ou não animais se nota uma diferença de R$ 116,241. Agora basta saber se essa diferença pode ser gerada aleatoriamente ou não.
casas_baratas=df[df$tipo=="Barata",]
media_c_animais=mean(casas_baratas$valor_aluguel[casas_baratas$animal==1], trim = 0.1)
media_s_animais=mean(casas_baratas$valor_aluguel[casas_baratas$animal==0], trim = 0.1)
c(media_c_animais, media_s_animais)
## [1] 1566.734 1450.495
Media_diff= media_c_animais- media_s_animais
Plotando o bloxplot para analisar a distribuição dos dados em torno da sua mediana não foi possível encontrar um deslocamento discrepante que possa justificar uma significancia da diferença encontrada entre os valores.
ggplot(data=casas_baratas, aes_string(x=casas_baratas$animal, y=casas_baratas$valor_aluguel, group=casas_baratas$animal)) + geom_boxplot() + theme_classic() + labs(x="Mobiliado ou não", y="Valor do aluguel", title = "Distribuição do aluguel de casas baratas pelo fator de ser mobiliado")
Avaliando agora o teste de permutação aleatória é possível notar que a diferença entre os valores do aluguel é significativa, mostrando que o acaso tem uma grande chance de não ter causado essa diferença.
#tamanho da amostra de mobiliados (3894)
length(casas_baratas$valor_aluguel[casas_baratas$animal==1])
## [1] 3894
#Tamanho da amostra de não mobiliados (1275)
length(casas_baratas$valor_aluguel[casas_baratas$animal==0])
## [1] 1275
#Tamanho total de 5169
#Criando funçãod e permutação considerado hipotese h0
perm_fun = function(x,n1,n2){
n= n1 + n2
idx_b= sample(1:n, n1)
idx_a= setdiff(1:n, idx_b)
mean_diff= mean(x[idx_b], trim = 0.1)- mean(x[idx_a], trim = 0.1)
return (mean_diff)
}
perm_diffs= rep(0,1000)
for(i in 1:1000)
perm_diffs[i]= perm_fun(casas_baratas$valor_aluguel, 3894,1275)
hist(perm_diffs, xlim=c(-100,500),nclass = 80)
abline(v= (Media_diff))
#valor p
p=mean(perm_diffs>Media_diff)
p
## [1] 0
casas_caras=df[df$tipo=="Caro",]
media_mobiliado=mean(casas_caras$valor_aluguel[casas_caras$animal==1], trim = 0.1)
media_n_mobiliado=mean(casas_caras$valor_aluguel[casas_caras$animal==0], trim = 0.1)
c(media_n_mobiliado, media_mobiliado)
## [1] 5194.056 5619.596
Media_diff= media_mobiliado- media_n_mobiliado
#tamanho da amostra de mobiliados (3894)
length(casas_caras$valor_aluguel[casas_caras$animal==1])
## [1] 4422
#Tamanho da amostra de não mobiliados (1275)
length(casas_caras$valor_aluguel[casas_caras$animal==0])
## [1] 1101
#Tamanho total de 5169
#Criando funçãod e permutação considerado hipotese h0
perm_fun = function(x,n1,n2){
n= n1 + n2
idx_b= sample(1:n, n1)
idx_a= setdiff(1:n, idx_b)
mean_diff= mean(x[idx_b], trim = 0.1)- mean(x[idx_a], trim = 0.1)
return (mean_diff)
}
perm_diffs= rep(0,1000)
for(i in 1:1000)
perm_diffs[i]= perm_fun(casas_baratas$valor_aluguel, 3894,1275)
hist(perm_diffs, xlim=c(-100,500),nclass = 80)
abline(v= (Media_diff))
#valor p
p=mean(perm_diffs>Media_diff)
p
## [1] 0
Com um p-value de 0 reijeitamos a hipotese nula de que essa diferença possa ter ocorrido devido ao acaso, logo a diferença é estatisticamente significativa.
Todos os passos a seguir são referentes a etapa de criação do modelo de regressão, previsão e análise dos pressupostos.
Para criar o modelo e evitar possíveis problemas com heteroscedasticidades nos resíduos foi decidido retirar os valores extremos da feature valor_aluguel, a qual decidimos fazer as previsões. Abaixo segue um boxplot mostrando a distribuição dos dados e os outlines que essa variável possui.
ggplot(df,aes(y=valor_aluguel))+
geom_boxplot() + labs(title = "Distribuição dos valores de alugueis")+
theme_classic()
Foi encontrado um total de 715 valores extremos que correspondem a apoximadamente 6% da coluna valor_aluguel. Devido a isso foi decidido remover essas linhas tendo em vista que ela não iria comprometer o modelo de regressão linear criado, foi utilizado como base para corte o intervalo inter-quartil.
n=as.data.frame(boxplot.stats(df$valor_aluguel)$out)
dim(n)
## [1] 715 1
q1= quantile(df$valor_aluguel,0.25)
q3= quantile(df$valor_aluguel,0.75)
iqr= IQR(df$valor_aluguel)
lim_superior= q1 + 1.5*(iqr)
lim_inferior= q3 - 1.5*(iqr)
df=df[(df$valor_aluguel < lim_superior) & (df$valor_aluguel> lim_inferior),]
ggplot(df,aes(y=valor_aluguel))+
geom_boxplot() + labs(title = "Distribuição dos valores de alugueis")+
theme_classic()
Olhando a base de dados é possível notar variações grandes de escala entre as features do dataset e isso pode acabar afetando o graú de importância que cada variável tem na regressão linear. Para evitar esse problema decidimos normalizar todas as variáveis do tipo númericas onde os valores para que variem entre -1 e 1.
colnumericas=sapply(df, is.numeric)
df[,colnumericas]=NormData(df[,colnumericas], type = 2)
Antes de fazer a seleção de features e a criação do modelo, criaremos uma função que separará o nosso conjunto de dados em um dataset de treino e outro de teste, 70% dos dados serão utilizados para treinar o modelo e 30% para avaliar a eficácia do mesmo.
df$tipo=NULL
teste_treino <- function(df){
split<- createDataPartition(df$cidade,p=0.7, list=F)
return (split)
}
split= teste_treino(df)
dados_treino=df[split,]
dados_teste=df[-split,]
Para garantir um modelo otimizado é necessário escolher as variáveis preditoras que serão utilizadas, essa etapa se faz importante para identificar o número mínimo de features que garantem uma acurácia alta. Pensando nisso será utilizado as seguintes métricas: -> A métrica AIC(Akaike’s Information Criteria), que penaliza a adição de termos a um modelo, onde através de uma regressão passo a passo serão incluidas e excluidas variáveis até que se encontre um modelo que diminua o AIC. -> CP de masllow onde ele compara a precisão e o vício do modelo completo a modelos com um subconjunto de preditores.
completo= lm(valor_aluguel~., data = dados_treino)
modelo1=step(completo, data=dados_treino, direction="backward", trace=F)
summary(modelo1)
##
## Call:
## lm(formula = valor_aluguel ~ cidade + area + quartos + banheiros +
## n_estacionamento + andar + animal + Furniture + taxa_incendio,
## data = dados_treino)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.76445 -0.07207 -0.01719 0.04295 2.02784
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.316336 0.007022 -45.049 < 2e-16 ***
## cidadeCampinas 0.068809 0.008575 8.025 1.21e-15 ***
## cidadePorto Alegre -0.126145 0.007899 -15.970 < 2e-16 ***
## cidadeRio de Janeiro 0.073454 0.007738 9.493 < 2e-16 ***
## cidadeSão Paulo 0.132324 0.006469 20.454 < 2e-16 ***
## area -0.004337 0.001840 -2.357 0.018463 *
## quartos -0.004369 0.002907 -1.503 0.132858
## banheiros 0.020477 0.003065 6.680 2.60e-11 ***
## n_estacionamento -0.012156 0.002648 -4.590 4.52e-06 ***
## andar1 0.283003 0.007277 38.888 < 2e-16 ***
## andar10 0.324118 0.011418 28.387 < 2e-16 ***
## andar11 0.325274 0.012405 26.222 < 2e-16 ***
## andar12 0.319562 0.013890 23.006 < 2e-16 ***
## andar13 0.348669 0.014288 24.403 < 2e-16 ***
## andar14 0.333804 0.015696 21.267 < 2e-16 ***
## andar15 0.317827 0.018744 16.957 < 2e-16 ***
## andar16 0.317426 0.020756 15.293 < 2e-16 ***
## andar17 0.335537 0.021724 15.446 < 2e-16 ***
## andar18 0.312278 0.023912 13.059 < 2e-16 ***
## andar19 0.400177 0.028773 13.908 < 2e-16 ***
## andar2 0.299852 0.007676 39.064 < 2e-16 ***
## andar20 0.458636 0.035747 12.830 < 2e-16 ***
## andar21 0.360596 0.035749 10.087 < 2e-16 ***
## andar22 0.355671 0.053370 6.664 2.89e-11 ***
## andar23 0.281288 0.043659 6.443 1.26e-10 ***
## andar24 0.292725 0.047790 6.125 9.61e-10 ***
## andar25 0.303784 0.037925 8.010 1.36e-15 ***
## andar26 0.258763 0.040533 6.384 1.85e-10 ***
## andar27 0.482343 0.087034 5.542 3.11e-08 ***
## andar28 0.378274 0.106492 3.552 0.000385 ***
## andar3 0.306092 0.007746 39.515 < 2e-16 ***
## andar301 0.352029 0.150513 2.339 0.019375 *
## andar4 0.317422 0.008296 38.261 < 2e-16 ***
## andar5 0.310275 0.009171 33.831 < 2e-16 ***
## andar51 0.283248 0.150549 1.881 0.059960 .
## andar6 0.308680 0.009352 33.006 < 2e-16 ***
## andar7 0.311947 0.009933 31.404 < 2e-16 ***
## andar8 0.317810 0.010040 31.654 < 2e-16 ***
## andar9 0.340501 0.011399 29.871 < 2e-16 ***
## animal -0.008246 0.001974 -4.176 3.00e-05 ***
## Furniture 0.015442 0.002057 7.508 6.84e-14 ***
## taxa_incendio 0.983046 0.002743 358.364 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1504 on 6194 degrees of freedom
## Multiple R-squared: 0.9773, Adjusted R-squared: 0.9771
## F-statistic: 6491 on 41 and 6194 DF, p-value: < 2.2e-16
A regressão encontrada se mostrou significativa pelo teste T para todos parâmetros, e conseguiu explicar boa parte da variação do valor do aluguel obtendo um R2 ajustado de 97,76%. #### Critério de cp Masllow
split=teste_treino(df)
dados_treino= df[split,]
dados_teste= df[-split,]
rs=summary(regsubsets(valor_aluguel~., nbest=2,data = dados_treino))
#rs$outmat
n_parametros= as.numeric(rownames(rs$which)) +1
#n_parametros
cp= rs$cp
#s2=rs$rss
R2_ajustado= rs$adjr2
#plot(n_parametros,cp,xlab = "Numero de parametros", ylab = "Estatistica Cp")
#plot(n_parametros,s2,xlab="N parametros",ylab = "S2")
plot(n_parametros,R2_ajustado)
#n_variaveis=n_parametros-1
#cbind(n_variaveis,n_parametros,cp,R2_ajustado,s2)
modelo2=lm(valor_aluguel~taxa_incendio + cidade +Furniture + area,dados_treino)
summary(modelo2)
##
## Call:
## lm(formula = valor_aluguel ~ taxa_incendio + cidade + Furniture +
## area, data = dados_treino)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.91025 -0.07639 0.00283 0.07781 1.98817
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.083287 0.006944 -11.994 < 2e-16 ***
## taxa_incendio 0.962790 0.002607 369.376 < 2e-16 ***
## cidadeCampinas 0.064008 0.010577 6.052 1.52e-09 ***
## cidadePorto Alegre -0.104237 0.009651 -10.800 < 2e-16 ***
## cidadeRio de Janeiro 0.128755 0.009265 13.897 < 2e-16 ***
## cidadeSão Paulo 0.136966 0.007749 17.676 < 2e-16 ***
## Furniture 0.037231 0.002414 15.424 < 2e-16 ***
## area -0.010789 0.002230 -4.839 1.34e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1878 on 6228 degrees of freedom
## Multiple R-squared: 0.9651, Adjusted R-squared: 0.9651
## F-statistic: 2.462e+04 on 7 and 6228 DF, p-value: < 2.2e-16
Para escolher o número de variáveis do modelo foi utilizado o gráfico do R2_ajustado onde com 4 variáveis o R2 não tem mais ganhos aparentes. A regressão encontrada se mostrou significativa pelo teste T para todos parâmetros, e conseguiu explicar boa parte da variação do valor do aluguel obtendo um R2 ajustado de 96,45%.
Serão avaliados dois modelos de regressão linear, modelo1 obtido utilizando o método de Akaike e o modelo2 que foi utilizado o críterio de masllow.
Para o modelo 1 foi decidido continuar com todas as 11 variáveis inicialmente colocadas no modelo, já o modelo2 foi decidido ficar apenas com apenas 4 variáveis.
Embora o modelo2 e o modelo1 tenham uma precisão muito próxima, decidimos testar a eficiência dos modelos para dados que eles não tiveram contato. O melhor modelo será aquele que trará melhores valores para as métricas R2 e RMSE. Para diminuir a influência do acaso foi utilizado o método bootstrap, onde serão testados exaustivamente diversos dados de teste e treino para coletar as métricas R2 e RMSE.
r2_modelo1=c()
RMSE_modelo1= c()
r2_modelo2= c()
RMSE_modelo2= c()
set.seed(0)
for (i in 1:20){
split= teste_treino(df)
dados_treino=df[split,]
dados_teste=df[-split,]
modelo1<- lm(valor_aluguel ~ cidade + quartos + banheiros + n_estacionamento + animal + Furniture + taxa_incendio,data=dados_treino)
#Com o modelo criado será utilizado os dados de testes para avaliar a precisão do modelo criado
previsao<- predict(modelo1,dados_teste)
#Dataframe com valor real e valor previsto
score1= as.data.frame(cbind(dados_teste$valor_aluguel,previsao))
names(score1)=c("Valor_real","Valor_previsto")
head(score1)
#Calculando o R2 para os modelo
r2_modelo1 = rbind(r2_modelo1,R2(score1$Valor_previsto, score1$Valor_real))
RMSE_modelo1= rbind(RMSE_modelo1,RMSE(score1$Valor_previsto, score1$Valor_real))
split= teste_treino(df)
dados_treino=df[split,]
dados_teste=df[-split,]
modelo2 = lm(formula = valor_aluguel ~ taxa_incendio + cidade +Furniture + area, data = dados_treino,
na.action = na.omit)
#Com o modelo criado será utilizado os dados de testes para avaliar a precisão do modelo criado
previsao<- predict(modelo2,dados_teste)
#Dataframe com valor real e valor previsto
score2= as.data.frame(cbind(dados_teste$valor_aluguel,previsao))
names(score2)=c("Valor_real","Valor_previsto")
head(score2)
#Calculando o R2 para os modelo
r2_modelo2 = rbind(r2_modelo2,R2(score2$Valor_previsto, score2$Valor_real))
RMSE_modelo2= rbind(RMSE_modelo2,RMSE(score2$Valor_previsto, score2$Valor_real))
}
c(mean(r2_modelo1),mean(r2_modelo2))
## [1] 0.9634122 0.9621905
Avaliando o R2 temos que o primeiro modelo foi um pouco melhor, embora a diferênça encontrada não é muito significativa para decidir qual modelo é melhor.
c(mean(RMSE_modelo1), mean(RMSE_modelo2))
## [1] 0.1906002 0.1935386
Utilizando o Erro médio quadratico o modelo 1 também teve o maior desemprenho, embora a diferênça também não seja significativa. Como os resultados encontrados foram praticamente iguais decidimos utilizar o critério da parcimônia que visa escolher o modelo mais simples caso a diferença dos resultados encontrados sejam próximos, logo será escolhido o modelo2 que utilizam apenas 4 variáveis para explicar o valor do aluguel.
Esta primeira hipotese é verdadeira, o software R garante que o modelo criado tenha coeficientes lineares.
par(par(mfrow=c(1,2)))
plot(valor_aluguel~taxa_incendio, main="Valor Aluguel Vs Taxa de Incendio",df)
plot(valor_aluguel~area, main="Valor Aluguel vs área",df)
Uma das principais premissas para um modelo de regressão linear é que os resíduos sigam uma distribuição normal, inicialmente plotamos o histograma para avaliar gráficamente se os dados são normais. É possível notar que embora ele tenha toda a forma de sino caracteristica desse tipo de distribuição, as caudas são longas decorrentes valores considerados extremos (Embora tenhamos retirado valores extremos da coluna valo_aluguel, as outras variáveis também possuiam valores outlines)
par(mfrow=c(1,2))
hist(modelo2$residuals, main="Histograma dos Residuos", breaks = 50)
qqnorm(modelo2$residuals, main = "QQ-Norm")
qqline(modelo2$residuals)
O gráfico qq normal deixa claro que os resíduos do modelo não seguem uma distribuição normal, mas para se afimar com certeza foi feito o teste de shapiro wilk para as primeiras 5000 observaçõs (limite máximo que o teste suporta).
shapiro.test(modelo1$residuals[1:5000])
##
## Shapiro-Wilk normality test
##
## data: modelo1$residuals[1:5000]
## W = 0.87234, p-value < 2.2e-16
Utilizando um nivel de confiança de 95% o teste de shapiro gerou um p-value de praticamente 0, indicando que podemos rejeitar a hipótese nula de que os dados seguem uma distribuição normal.
O primeiro indício da baixa multicolinearidade entre as variáveis regressoras se pode observar analisando os coeficientes do modelo que deram todos significativos para o teste t. Porém para se ter mais certeza será utilizado o FIV (Fator de inflação de variância) para avaliar a relação entre as variáveis explicativas.
vif(modelo2)
## taxa_incendio cidadeCampinas cidadePorto Alegre
## 1.790567 1.614867 1.847294
## cidadeRio de Janeiro cidadeSão Paulo Furniture
## 1.984109 2.692913 1.084470
## area
## 1.690016
As variáveis explicativas estão com um fator de inflação baixos, como o valor do FIV para as features deram no máximo 2.69, pela classificação do método podemos afirmar que existe uma baixa multicolinearidade entre as features.
Analisando o plot dos residuos do modelo não é possível avaliar bem o gráfico devido a quantidade alta de pontos, mesmo assim da para notar um desajuste nos resíduos, parece existir mais pontos subestimados do que superstimados gerando mais pontos extremos positivos que negativos e isso pode ser um indício de heteroscedasticidade.
plot(modelo2$residuals, main="Resíduos x Valores Ajustados", xlab="Valores",ylab="Resíduos")
Pelo teste de Breusch Pagan, onde temos a hipotese Ho indicando que os resíduos são homoscedásticos e H1 indicando que são heterocedásticos.
bptest(modelo2)
##
## studentized Breusch-Pagan test
##
## data: modelo2
## BP = 167.14, df = 7, p-value < 2.2e-16
O teste gerou um p-value menor que 0.05, logo para um nível de confiança de 95% podemos rejeitar a hipótese nula de que os resíduos sao homoscedásticos.
Para uma análise mais formal foi utilizado o teste estátistico de Durbin Watson onde a Hipótese nula Ho indica a ausência de autocorrelação e a hipótese alternativa H1 a presença de autocorrelação. Para um nivel de significancia de 95% foi encontrado um p-value acima de 0.05, logo não podemos rejeitar a hipótese nula de que os resíduos não possúem autocorrelação.
dwtest(modelo2)
##
## Durbin-Watson test
##
## data: modelo2
## DW = 1.9766, p-value = 0.178
## alternative hypothesis: true autocorrelation is greater than 0
Como foi dito anteriormente, foi utilizado uma parte dos dados para criar o modelo e outra para testar a eficiência da regressão quando ela tenta prever valores novos. Abaixo segue uma pequena análise dos resíduos gerados quando o modelo tenta prever valores de alugueis que ele não teve contato.
residuo<- function(real,previsto){
return(previsto-real)
}
residu=as.data.frame(mapply( residuo,score2$Valor_real,score2$Valor_previsto))
names(residu)="residuo"
par(mfrow=c(1,2))
hist(residu$residuo, main="Histograma dos Residuos", breaks = 50)
qqnorm(residu$residuo, main = "QQ-Norm")
qqline(residu$residuo)
O histograma mostra que o modelo tem a maioria das suas previsões próxima a zero o que indica uma alta precisão gerando um baixo erro, porém ele tende a superestimar os valores dos alugueis em alguns casos gerando um leve aumento aparente no lado direito do histograma. O gráfico qq normal mostra que tem uma grande chance de os resíduos não serem normais semelhante a análise feita dos erros gerados no modelo mostrados durante os pressupostos. Acreditamos que esse problema se dá devido a falta de uma feature muito importante que é a localização da casa nas cidades causando heterocedasticidade e a não normalidade.
Em seguida foi plotado os valores reais e previstos em um gráfico para visualizar a diferença entre os valores. É possível notar que os valores ficaram bem proximos um dos outros, mostrando que a análise foi bastante eficaz. Foi mostrado apenas os 200 primeiros valores previstos por motivos de melhor visualização.
score2$index=c(1:2670)
ggplotly(ggplot(data=score2, aes(x=index,y=Valor_real, col="Real"))+ geom_line() + labs(title = "Valores Previstos e reais", x="Observações", y="Valores") +geom_line(data=score2, aes(x=index, y=Valor_previsto, col="Previsto")) +theme_classic() + xlim(c(0,200)) + scale_color_manual(values = c("black","red")) + guides(color=guide_legend(title=""))
)