PD da disciplina ‘Análise exploratória de dados [26E1_3]’

# carregar as bibliotecas
options(OutDec = ",")
library(tidyverse)
library(skimr)
library(DT)
library(summarytools)
library(knitr)
library(scales)
library(patchwork)
library(corrplot)
library(gtsummary)
library(effectsize)
library(kableExtra)
library(nortest)
library(rstatix)

1. O relatório final deve ser apresentado utilizando o Quarto. Nesse relatório devem haver:

  • imagens estáticas (“prints” de tela, imagens da internet - com a devida fonte mencionada - ou figuras criadas pelo aluno fora do ambiente do R);

  • imagens geradas através do ambiente R, particularmente com a biblioteca ggplot;

  • links clicáveis (como fontes e referências).


Resposta: neste trabalho, não foram utilizadas imagens estáticas oriundas da internet ou de fora do ambiente do R, apenas imagens geradas dentro do R (gráficos) e links clicáveis da base de dados, do painel shiny e deste relatório publicado no RPubs.

2. Escolha uma base de dados para realizar esse projeto. Essa base de dados será utilizada durante toda sua análise. Essa base necessita ter 4 (ou mais) variáveis de interesse, onde todas são numéricas (confira com o professor a possibilidade de utilização de dados categóricos). Observe que é importante que haja dados faltantes em pelo menos uma variável para executar esse projeto. Caso você tenha dificuldade para escolher uma base, o professor da disciplina irá designar para você. Explique qual o motivo para a escolha dessa base e aponte os resultados esperados através da análise.


Resposta: foi escolhida a base de dados de imóveis transacionados na cidade de São Paulo (SP) entre maio/22 e out/22, disponível no Kaggle (https://www.kaggle.com/datasets/juliotorniero/real-estate-transactions-sao-paulo-brazil). O motivo de a escolha desta base reside no interesse de explorar uma base como muitas variáveis e observações. Os resultados esperados consistem em avaliar como as características dos imóveis e a localização influenciam o seu preço.

A base possui 86.695 transações (linhas) e 24 variáveis (colunas). As variáveis são as seguintes:

  • tax_id: código identificador da propriedade nos registros da prefeitura, para fins tributários;

  • street_name: nome da rua na qual a propriedade está localizada;

  • street_number: número do imóvel (na rua);

  • complement: complemento do endereço, como número do apartamento, bloco, torre, entre outros;

  • district: zona ou distrito da cidade de São Paulo em que a propriedade está localizada;

  • reference: referência geral da propriedade;

  • zip_code: CEP da propriedade;

  • transaction_nature: motivação legal para a transação, como compra e venda, transmissão de direitos, transferência entre pessoa e empresa, entre outras;

  • transaction_value_BRL: valor da transação em reais;

  • date: data da transação;

  • cadastral_value: valor da propriedade nos registros da prefeitura, em reais;

  • tax_base_value: valor base da transação para cálculo de impostos, em reais;

  • mortgage_type: tipo de hipoteca, se houver alguma;

  • mortgage_value: valor em reais da hipoteca, se houver alguma;

  • registry_number: código do cartório de registro de imóveis em que a transação foi registrada;

  • property_id: código de identificação da propriedade no cartório de registro de imóveis;

  • city_hall_status: situação da propriedade de acordo com a prefeitura;

  • land_area_m2: área da propriedade, em metros quadrados;

  • front_length_m: comprimento da propriedade no lado virado para a rua, em metros;

  • ideal_fraction: fração transacionada do total da propriedade;

  • area_built_m2: área construída, em metros quadrados;

  • description_1: descrição da ocupação do imóvel (loja, terra, apartamento, entre outras);

  • description_2: tipo de propriedade (comercial vertical, comercial horizontal, terra, industrial, entre outros); e

  • year_built: ano de conclusão da construção.

O código a seguir mostra o tipo das variáveis, a quantidade de valores faltantes em cada uma e as estatísticas descritivas para as variáveis numéricas.

df <- read_delim("sao_paulo_real_estate.csv", delim = ",")
df %>% skim()
Data summary
Name Piped data
Number of rows 86695
Number of columns 24
_______________________
Column type frequency:
character 12
numeric 12
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
tax_id 0 1,00 14 14 0 58150 0
street_name 0 1,00 5 38 0 12441 0
complement 12377 0,86 1 18 0 38910 0
district 31990 0,63 1 22 0 7500 0
reference 49000 0,43 1 20 0 11490 0
zip_code 0 1,00 8 8 0 15305 0
transaction_nature 0 1,00 2 31 0 27 0
date 0 1,00 10 10 0 775 0
mortgage_type 0 1,00 10 17 0 4 0
city_hall_status 0 1,00 10 18 0 5 0
description_1 0 1,00 1 24 0 24 37
description_2 0 1,00 4 21 0 10 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
street_number 0 1,00 10826,95 29967,22 0 155,00 403,00 1085,0 99999,00 ▇▁▁▁▁
transaction_value_BRL 0 1,00 654503,89 3371283,00 0 216500,00 300000,00 550000,0 521521053,00 ▇▁▁▁▁
cadastral_value 0 1,00 414027,20 2590716,32 0 28854,00 190035,00 397222,0 417769695,00 ▇▁▁▁▁
tax_base_value 0 1,00 696138,41 3424367,78 0 230000,00 321616,00 595848,0 521521053,00 ▇▁▁▁▁
mortgage_value 0 1,00 106396,95 179729,24 -604339 0,00 0,00 189000,0 17300000,00 ▇▁▁▁▁
registry_number 0 1,00 10,12 4,69 1 7,00 11,00 14,0 18,00 ▃▃▇▃▆
property_id 0 1,00 182018,43 149074,70 0 93305,50 163212,00 241268,5 9223536,00 ▇▁▁▁▁
land_area_m2 0 1,00 6579,66 21139,49 22 500,00 1755,00 5042,5 1669181,00 ▇▁▁▁▁
front_length_m 0 1,00 49,34 77,29 0 10,00 26,00 60,0 4041,88 ▇▁▁▁▁
ideal_fraction 0 1,00 0,51 0,49 0 0,01 0,33 1,0 1,00 ▇▁▁▁▇
area_built_m2 0 1,00 875,03 4530,98 0 48,00 101,00 200,0 164615,00 ▇▁▁▁▁
year_built 14433 0,83 1992,90 19,24 1919 1978,00 1994,00 2011,0 2022,00 ▁▁▅▇▇


A tabela anterior mostra que não há nenhuma variável carregada como caracter que precisa ser transformada em variável numérica.

Além disso, nota-se que a base possui muitos valores faltantes, concentrados nas variáveis reference (49.000 valores faltantes), district (31.990), year_built (14.433) e complement (12.377).

No caso das variáveis reference, district e complement, a descrição da base no Kaggle não explica o significado destes valores faltantes, mas pode ser em decorrência da propriedade não possuir referência, não estar localizada em um distrito específico e também não ter nenhum complemento de endereço. A tabela a seguir mostra que os valores faltantes destas variáveis não estão concentrados em nenhuma categoria da variável description_1.

# calcular a quantidade de valores faltantes para as variáveis categóricas por 
# descrição da propriedade
df_na_categ <- df %>% group_by(description_1) %>% 
  summarise(across(c(reference, district, complement), ~sum(is.na(.)),
            .names = "{.col}"
    )) %>% ungroup()

# criar tabela para exibição
datatable(
  df_na_categ,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Quantidade de valores faltantes x descrição da propriedade'),
  colnames = c(
    "Descrição" = "description_1"
  ),
  options = list(
    scrollX = TRUE,
    scrollY = "440px",
    pageLength = 10,
    dom = 'tp'
  ),
)


A tabela anterior mostra que há um valor na coluna de descrição que não é faltante, mas está em branco, podendo ser um espaço. O código a seguir mostra os valores únicos desta coluna e confirma a existência de um espaço em branco.

valores_unicos <- unique(df$description_1)

cat(paste0("- '", valores_unicos,"'"), sep = "\n")
- 'store'
- 'others'
- 'land'
- 'hotel'
- 'industry'
- 'house'
- 'apartment'
- 'garage'
- 'workshop'
- 'office'
- 'hotel_flat'
- 'twinning_house'
- 'school'
- 'apartment_flat'
- 'social_usage'
- 'warehouse'
- 'hospital'
- 'service_post'
- 'store_on_building_ground'
- 'temple'
- ' '
- 'press'
- 'tenement'
- 'entertainment_usage'

Este espaço foi substituído por “nao_informado” e tabela foi refeita, conforme o código abaixo.

# substituir o espaço em branco
df <- df %>% mutate(
  description_1 = if_else(description_1 == " ", "nao_informado", description_1)
)

# calcular a quantidade de valores faltantes para as variáveis categóricas por 
# descrição da propriedade
df_na_categ <- df %>% group_by(description_1) %>% 
  summarise(across(c(reference,district, complement), ~sum(is.na(.)),
            .names = "{.col}"
    )) %>% 
  arrange(description_1) %>% ungroup()

# criar tabela para exibição
datatable(
  df_na_categ,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Quantidade de valores faltantes x descrição da propriedade'),
  colnames = c(
    "Descrição" = "description_1"
  ),
  options = list(
    scrollX = TRUE,
    scrollY = "440px",
    pageLength = 10,
    dom = 'tp'
  ),
)


A tabela acima mostra que os valores faltantes de reference, district e complement não estão concentrados em algum tipo de propriedade, o que não ajuda a explicar estes valores.

A tabela abaixo mostra a quantidade de valores faltantes nas três variáveis citadas para as dez ruas com mais valores faltantes para a variável reference, o que também não mostra concentração em algumas ruas.

# calcular a quantidade de valores faltantes por rua
df_na_categ_2 <- df %>% group_by(street_name) %>% 
  summarise(across(c(reference, district, complement), ~sum(is.na(.)),
            .names = "{.col}"
    )) %>% 
  arrange(desc(reference), desc(district), desc(complement)) %>% ungroup()

# criar tabela para exibição
datatable(
  head(df_na_categ_2, 10),
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Quantidade de valores faltantes x nome da rua'),
  colnames = c(
    "Nome da rua" = "street_name"
  ),
  options = list(
    scrollX = TRUE,
    scrollY = "440px",
    pageLength = 10,
    dom = 't'
  ),
)


Tendo em vista que são muitas observações com valores faltantes e sem explicação, optou-se por substituir os mesmos por “nao_informado”. O código a seguir realiza esta substituição.

df <- df %>% mutate(
  across(c(reference, district, complement), ~replace_na(., "nao_informado"))
)

Em se tratando da variável numérica year_built, a tabela abaixo mostra que todos os valores faltantes estão concentrado na descrição de propriedade land (terreno), o que faz sentido, pois trata-se de propriedade sem imóvel edificado, não possuindo o ano de finalização da construção.

# calcular a quantidade de valores faltantes de year_built por descrição
year_na <- df %>% group_by(description_1) %>% 
  summarise(year_built_nr_na = sum(is.na(year_built))) %>% 
  ungroup()

# criar a tabela
datatable(
  year_na,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Quantidade de valores faltantes x descrição da propriedade'),
  colnames = c(
    "Descrição" = "description_1",
    "year_built" = "year_built_nr_na"
  ),
  options = list(
    scrollX = TRUE,
    scrollY = "440px",
    pageLength = 10,
    dom = 'tp'
  ),
)


Como os valores faltantes dessa coluna são legítimos, ou seja, a informação não foi omitida, ela realmente não existe para propriedades do tipo land, é necessário manter essa variável e estas observações na base. A alternativa encontrada para tratar esse problema, de modo a não afetar possíveis modelos de regressão feitos com essa variável, foi substituir os valores faltantes por zero e criar uma variável binária chamada is_land que será 1 para propriedade do tipo land e zero para as demais. Com isso, as duas variáveis (year_built e is_land) devem ser colocadas juntas no modelo e não haverá viés na regressão:

  • para propriedades edificadas (is_land = 0), o modelo ignora o coeficiente de is_land e usa o year_built (exemplo 1990, 2010) para prever o preço; e

  • para propriedades igual a land (is_land=1), o modelo usa o coeficiente da variável is_land para ajustar o preço base da propriedade. Como o year_built é zero, o impacto dessa variável é anulado matematicamente para esses casos (\(0 \times B_{year\_built} = 0\)).

df <- df %>% mutate(
  is_land = if_else(description_1 == "land", 1, 0),
  year_built = replace_na(year_built, 0)
)

3. Utilizando o pacote summarytools (função descr), descreva estatisticamente a sua base de dados.


Resposta: para calcular as estatísticas descritivas da base, foram selecionadas apenas as variáveis numéricas para as quais há sentido no cálculo destas estatísticas, o que exclui variáveis numéricas identificadoras e outras que, apesar de serem numéricas, representam categorias. Isto posto, as seguintes variáveis numéricas foram excluídas deste cálculo: street_number, registry_number e property_id.

# descrever a base
descr_num <- df %>% select(where(is.numeric) & !c(street_number, registry_number, property_id)) %>% descr()

# arredondar para duas casas decimais
descr_num <- round(descr_num, 2)

# criar uma tabela com scroll
datatable(
  descr_num,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Estatísticas descritivas das variáveis numéricas'),
  options = list(
    scrollX = TRUE,
    scrollY = "450px",
    pageLength = 10,
    dom = 't'
  ),
)


A tabela gerada mostra algumas variáveis com valor mínimo igual a zero. A tabela a seguir calcula a quantidade de valores iguais a zero nas variáveis area_built_m2, cadastral_value, front_length_m, ideal_fraction, tax_base_value, transaction_value_BRL e year_built.

# calcular a quantidade de zeros
df_zeros_total <- df %>%
  summarise(across(
    c(area_built_m2, cadastral_value, front_length_m, ideal_fraction, tax_base_value, transaction_value_BRL, year_built),        
    ~ sum(. == 0, na.rm = TRUE), 
    .names = "{.col}"
  ))

# gerar a tabela
datatable(
  df_zeros_total,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Quantidade de valores iguais a zero nas colunas selecionadas'),
  rownames = NULL,
  options = list(
    scrollX = TRUE,
    scrollY = "100px",
    pageLength = 10,
    dom = 't'
  ),
)


Em se tratando das variáveis area_built_m2 e year_built, o valor zero é decorrente da propriedade ser land (terreno), sem construção edificada. Para as demais variáveis, constata-se a existência de zeros em diversas categorias da coluna description_1, o que não permite explicar a existência destes valores por meio da coluna citada. A tabela abaixo mostra a quantidade de zeros por descrição das propriedades.

# gerar a tabela com a quantidade de zeros para as variáveis com esse valor mínimo
df_zeros <- df %>%
  group_by(description_1) %>%
  summarise(across(
    c(area_built_m2, cadastral_value, front_length_m, ideal_fraction, tax_base_value, transaction_value_BRL, year_built),        
    ~ sum(. == 0, na.rm = TRUE), 
    .names = "{.col}"
  ))

# gerar a tabela
datatable(
  df_zeros,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Quantidade de valores iguais a zero x descrição'),
  colnames = c(
    "Descrição" = "description_1"
  ),
  options = list(
    scrollX = TRUE,
    scrollY = "450px",
    pageLength = 10,
    dom = 'tp'
  ),
)


Há de se observar a coluna ideal_fraction, que na tabela anterior não apresenta nenhum valor igual a zero, mas na tabela de estatísticas descritivas apresenta zero como valor mínimo. Este fato é decorrente da coluna ter o valor mínimo de 0,0001, o qual foi arredondado para duas casas decimais na tabela e aparece como zero. Essa coluna representa a fração transacionada da propriedade, sendo que o valor 1 representa 100%. Portanto, 0,0001 representa 0,01% da propriedade que foi objeto de uma transação. Assim, não há evidências de erro nesta coluna pois o intervalo de valores está dentro do esperado, com o mínimo igual a 0,0001 e o máximo igual a 1.

A tabela a seguir mostra a quantidade de valores iguais a zero nas variáveis citadas anteriormente, mas agora por natureza da transação, a fim de avaliar se esta variável pode auxiliar na explicação dos valores iguais a zero. As variáveis ideal_fraction, year_built e area_built_m2 foram retiradas desta análise, em razão dos seus valores iguais a zero terem sido explicados anteriormente.

# quantidade de zeros por coluna
df_zeros_2 <- df %>%
  group_by(transaction_nature) %>%
  summarise(across(
    c(cadastral_value, front_length_m, tax_base_value, transaction_value_BRL),    
    ~ sum(. == 0, na.rm = TRUE), 
    .names = "{.col}"
  ))

# gerar a tabela
datatable(
  df_zeros_2,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Quantidade de valores iguais a zero x natureza da transação'),
  colnames = c(
    "Natureza da transação" = "transaction_nature"
  ),
  options = list(
    scrollX = TRUE,
    scrollY = "450px",
    pageLength = 10,
    dom = 'tp'
  ),
)


Como mostrado na tabela anterior, a quantidade de zeros nas colunas se distribui entre as várias categorias de natureza da transação, não ocorrendo concentração em uma única natureza. Portanto, o uso da natureza da transação também não contribuiu para explicar estes valores iguais a zero em colunas que normalmente se esperaria algum valor positivo, como o valor cadastral da propriedade, o valor da transação e o valor base da propriedade para fins tributários.

Em se tratando da variável front_length_m, o valor igual à zero pode ser explicado quando ocorre em propriedades do tipo land (terreno), mas não foi encontrada explicação para os zeros que se distribuem nas outras propriedades.

Para remover os zeros que não foram explicados, decidiu-se substituir esses valores das colunas cadastral_value, taxa_base_value, transaction_value_BRL e front_length_m pela mediana das respectivas variáveis calculadas por rua (street_name), área construída (area_built_m2) e por descrição da propriedade (description_1).

Assim, adotou-se a premissa de que o valor da propriedade (que, em essência, é o que consta nas variáveis cadastral_value, taxa_base_value e transaction_value_BRL) e a sua extensão em relação à rua (variável front_length_m) podem ser razoavelmente explicados pela localização do imóvel (imóveis em áreas nobres geralmente possuem maior valor do que imóveis em área periféricas), pela área construída (quanto maior a área construída, maior o valor do imóvel e a sua extensão em relação à rua) e pela descrição do imóvel (casas e apartamentos na mesma rua e com a mesma área construída podem ter valores e extensão em relação à rua diferentes).

Para calcular a mediana agrupada pela variável area_built_m2, que é uma variável numérica contínua, foi criada uma variável categórica, denominada area_built_m2_classe, que é formada por faixas de valores da área construída. A quantidade de faixas foi escolhida com base na estatística de Freedman-Diaconis, conhecida por ser mais robusta para distribuições assimétricas, que consiste no seguinte cálculo:

\(k = \frac{max(x) - min(x)}{h}\)

onde:

\(h = 2\frac{IQR}{n^{1/3}}\)

IQR é o intervalo interquartil e n é o número de observações.

O código abaixo substitui os zeros que não puderam ser explicados e gera nova tabela mostrando a quantidade de zeros.

# definir os componentes da estatística de Freedaman-Diaconis
n <- nrow(df)
iqr_val <- IQR(df$area_built_m2, na.rm = TRUE)

# calcular a largura da faixa (h)
h <- 2 * iqr_val / (n^(1/3))

# calcular o número de classes (k)
diff_range <- diff(range(df$area_built_m2, na.rm = TRUE))
k <- ceiling(diff_range / h)

# criar variável contendo os intervalos de área construída
df2 <- df %>%
  mutate(
    area_built_m2_classe = cut(area_built_m2, k)
    )

# calcular a mediana das variáveis com base no nome da rua, área construída e descrição
df2 <- df2 %>% group_by(street_name, area_built_m2_classe, description_1) %>% mutate(
  cadastral_value_mediana = median(cadastral_value),
  tax_base_value_mediana = median(tax_base_value),
  transaction_value_BRL_mediana = median(transaction_value_BRL),
  front_length_m_mediana = median(front_length_m)
) %>% ungroup()

# substituir os valores iguais a zero
df2 <- df2 %>% mutate(
  cadastral_value = if_else(cadastral_value == 0, cadastral_value_mediana, cadastral_value),
  tax_base_value = if_else(tax_base_value == 0, tax_base_value_mediana, tax_base_value),
  transaction_value_BRL = if_else(transaction_value_BRL == 0, transaction_value_BRL_mediana, transaction_value_BRL),
  front_length_m = if_else(front_length_m == 0 & description_1 != "land", front_length_m_mediana, front_length_m)
) %>% 
  select(
    -c(cadastral_value_mediana, tax_base_value_mediana, transaction_value_BRL_mediana, front_length_m_mediana)
  )

# gerar nova tabela de quantidade de zeros por variável
df_zeros_total_2 <- df2 %>%
  summarise(across(
    c(cadastral_value, front_length_m, tax_base_value, transaction_value_BRL),
    ~ sum(. == 0, na.rm = TRUE),
    .names = "{.col}"
  ))

# gerar a tabela
datatable(
  df_zeros_total_2,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Quantidade de valores iguais a zero'),
  rownames = NULL,
  options = list(
    scrollX = TRUE,
    scrollY = "100px",
    pageLength = 10,
    dom = 't'
  ),
)


A tabela anterior mostra que houve redução na quantidade de observações iguais a zero nas quatro variáveis em que foram realizadas as substituições pela mediana. Contudo, ainda permanecem alguns valores iguais a zero, que são decorrentes do valor da mediana destas variáveis, agrupada por rua, descrição e área construída, também ter sido zero. Após a substituição, ao se examinar uma amostra de observações com valores iguais a zero para a variável front_length_m, constatou-se que todos os casos pertenciam à grupos de apenas um registro de rua, descrição e classe de área construída, sendo que o valor de front_length_m nesses casos era zero, o que gerou a mediana igual zero. Ou seja, as variáveis responsáveis por auxiliar na estimação de valores diferentes de zero também geraram medianas iguais a zero.

Neste caso, a opção mais viável seria excluir as variáveis iguais a zero, a fim de que não prejudiquem a modelagem. O código abaixo realiza esta eliminação.

df2 <- df2 %>% filter(
  !if_any(c(cadastral_value, tax_base_value, transaction_value_BRL), ~.x == 0),
  !(front_length_m == 0 & description_1 != "land")
)

O problema com a eliminação realizada é que foram perdidas 7.844 observações, cerca de 9% do total de observações (86.695). Uma das explicações para a quantidade de observações eliminadas é a estatística de Freedman-Diaconis para calcular a quantidade de classes da variável area_built_m2. Ela gerou 23.966 classes, as quais possuem intervalo pequeno, o que faz com que muitas classes tenham apenas uma observação e que, infelizmente, é zero, gerando a mediana igual a zero. Porém, ao se avaliar a eliminação de observações considerando 3.000 classes, quase 10% da quantidade proposta pela estatística de Freedman-Diaconis, a quantidade de observações eliminadas seria de 7.274, ou seja, redução de apenas 7,3% em relação à redução gerada pela estatística de Freedman-Diaconis. Isto posto, aceitou-se a redução calculada inicialmente de 7.844 observações.

Outro problema identificado na tabela de estatísticas descritivas das variáveis numéricas foi a presença de um valor negativo para a variável mortgage_value. Para avaliar este valor, foi primeiramente adotada a hipótese de que há apenas um erro de sinal. Para se avaliar esta hipótese, foi calculada a razão entre mortgage_value e transaction_value_BRL, para esta observação com mortage_value negativo, a fim de se comparar com a razão dos demais registros. O cálculo é realizado no código abaixo.

# criar a razao
df2_rz_mortgage <- df2 %>% filter(mortgage_value == min(mortgage_value)) %>% 
  summarise(
    rz_mortgage_transaction = mortgage_value/transaction_value_BRL
  )

datatable(
  round(df2_rz_mortgage, 2),
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Razão entre mortgage_value e transaction_value_BRL para o valor negativo de mortgage_value'),
  rownames = NULL,
  options = list(
    scrollX = TRUE,
    scrollY = "100px",
    pageLength = 10,
    dom = 't'
  ),
)


O resultado foi -0,4277. No código a seguir, a base foi filtrada para manter apenas os registros com mortgage_value e transaction_value_BRL diferentes de zero e foi calculada a mesma razão anterior (rz_mortgage_transaction).

# filtrar os registros com valores de transação e de hipoteca diferentes de zero e excluir o valor negativo
df_mortgage <- df2 %>% filter(transaction_value_BRL != 0 & mortgage_value != 0) %>%
  filter(mortgage_value != min(mortgage_value)) %>% 
  mutate(
    rz_mortgage_transaction = mortgage_value/transaction_value_BRL
  )

# calcular a mediana
mediana_mort <- quantile(df_mortgage$rz_mortgage_transaction, probs = c(0.5))

# gerar a tabela
datatable(
  as_tibble(mediana_mort),
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Mediana de rz_mortgage_transaction'),
  rownames = NULL,
  colnames = c("Valor" = "value" ),
  options = list(
    scrollX = TRUE,
    scrollY = "100px",
    pageLength = 10,
    dom = 't'
  ),
)


Para se avaliar se 0,4277 é uma valor factível, ou seja, bastaria alterar o sinal para que ele ficasse correto, foi calculado o Intervalo Interquartil (IQR) e, em seguida, foi calculado o Limite Inferior (LI) que é utilizado geralmente nos gráficos Boxplot. A escolha do LI reside no fato de que a mediana de rz_mortgage_transaction foi 0,75. Como o valor de 0,4277 está abaixo da mediana, ele só pode ser outlier se ficar abaixo do LI. Como regra de decisão, se o valor de 0,4277 for considerado um outlier, ele será eliminado. Do contrário, será alterado apenas o sinal de mortgage_value e ele será mantido na base. O código abaixo calcula o LI.

# calcular os percentis 25 e 75
percentis <- quantile(df_mortgage$rz_mortgage_transaction, probs = c(0.25, 0.75))

# calcular o IQR
IQR <- percentis[[2]] - percentis[[1]]

# calcular o LI
lim_inf <- percentis[[1]] - IQR * 1.5

# gerar a tabela
datatable(
  round(as_tibble(lim_inf),2),
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Limite inferior de rz_mortgage_transaction'),
  rownames = NULL,
  colnames = c("Valor" = "value" ),
  options = list(
    scrollX = TRUE,
    scrollY = "100px",
    pageLength = 10,
    dom = 't'
  ),
)


Como o valor de 0,4277 não é inferior ao LI (0,34), ele não é considerado outlier e o valor negativo de mortgage_value será apenas alterado para positivo. O código abaixo realiza este procedimento.

df2 <- df2 %>% 
  mutate(
    mortgage_value = if_else(mortgage_value == min(mortgage_value), mortgage_value * -1, mortgage_value)
  )

Para finalizar, a tabela abaixo mostra as estatísticas descritivas após os ajustes realizados. As variáveis area_built_m2, front_length_m e year_built possuem valores iguais a zero quando se trata de propriedades do tipo land. A variável ideal_fraction possui valor mínimo igual a zero apenas por causa do arredondamento. E a variável mortgage_value possui valor mínimo igual a zero quando a propriedade não possui hipoteca.

# descrever a base
descr_num_2 <- df2 %>% select(where(is.numeric) & !c(street_number, registry_number, property_id, is_land)) %>% descr()

# arredondar para duas casas decimais
descr_num_2 <- round(descr_num_2, 2)

# criar uma tabela com scroll
datatable(
  descr_num_2,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Estatísticas descritivas das variáveis numéricas'),
  options = list(
    scrollX = TRUE,
    scrollY = "450px",
    pageLength = 10,
    dom = 't'
  ),
)

Detecção de outliers

A próxima etapa do trabalho de Análise Exploratória de Dados consistiu em avaliar a existência de outliers. As variáveis numéricas para as quais faz sentido gerar o Boxplot são as seguintes: cadastral_value, transaction_value_BRL, tax_base_value, mortgage_value, land_area_m2, front_length_m, ideal_fraction, area_built_m2 e year_built. O código abaixo gera uma função para criar os gráficos e, em seguida, esta função é chamada para cada uma das variáveis.

gera_box_plot <- function(variavel, titulo_graf, titulo_eixo_y, prefixo = NULL, sufixo = "k", escala=1e-03){
  
  var_plot <- rlang::enquo(variavel)
  grafico <-  df2 %>% ggplot(aes(y = !!var_plot)) +
  geom_boxplot(fill = "#E6F1FB", color = "#185FA5",
               outlier.color = "#C04828", 
               outlier.alpha = 0.5,
               width = 0.5) +
    scale_y_continuous(labels = label_number(prefix = prefixo, 
                                             scale = escala, 
                                             suffix = sufixo, 
                                             big.mark = ".", 
                                             decimal.mark = ",")) +
    labs(title = titulo_graf, y = titulo_eixo_y) +
    theme_minimal(base_size = 12) +
    theme(axis.text.x = element_blank(),
          plot.title = element_text(face = "bold"))
  return(grafico)
}
gera_box_plot(cadastral_value, 
              "Boxplot - cadastral_value (valor cadastral)", 
              "cadastral_value", 
              prefixo = "R$ ")

O gráfico anterior mostra que há muitos outliers em cadastral_value. A fim de não eliminar muitas observações, decidiu-se excluir apenas aquelas com cadastral_value maior que R$ 25.000k. O código abaixo realiza este ajuste.

df2 <- df2 %>% filter(
  cadastral_value < 25000000
)
gera_box_plot(transaction_value_BRL, 
              "Boxplot - transaction_value_BRL (valor da transação)", 
              "transaction_value_BRL", 
              prefixo = "R$ ")

A variável transaction_value_BRL também possui outliers. Decidiu-se eliminar as observações com valores maiores que R$ 50.000k. O código abaixo realiza este ajuste.

df2 <- df2 %>% filter(
  transaction_value_BRL < 50000000
)
gera_box_plot(tax_base_value, 
              "Boxplot - tax_base_value (valor base para impostos)",
              "tax_base_value", 
              prefixo = "R$ ")

O gráfico anterior também mostra que há muitos outliers em tax_base_value. De forma análoga ao caso anterior, decidiu-se excluir apenas aquelas observações com tax_base_value maior que R$ 20.000k. O código abaixo realiza este ajuste.

df2 <- df2 %>% filter(
  tax_base_value < 20000000
)
gera_box_plot(mortgage_value, 
              "Boxplot - mortgage_value (valor da hipoteca)", 
              "mortgage_value", 
              prefixo = "R$ ")

Também foram identificados muitos outliers em mortgage_value. Decidiu-se excluir apenas as observações com valores maiores de mortgage_value maiores que R$1.500k. O código abaixo realiza este ajuste.

df2 <- df2 %>% filter(
  mortgage_value < 1500000
)
gera_box_plot(land_area_m2, 
              "Boxplot - land_area_m2 (área do terreno)", "land_area_m2", 
              sufixo = "k m2")

Mantendo o padrão das variáveis anterior, também há muitos outliers em land_area_m2. Decidiu-se eliminar aqueles com valores maiores que 75k. O código abaixo realiza este procedimento.

df2 <- df2 %>% filter(
  land_area_m2 < 75000
)
gera_box_plot(front_length_m, 
              "Boxplot - front_length_m (comprimento na face da rua)",
              "front_length_m", 
              sufixo = "m", 
              escala = 1)

A variável front_length_m também possui outliers. Decidiu-se eliminar as observações com valores maiores que 400m. O código abaixo realizado o ajuste.

df2 <- df2 %>% filter(
  front_length_m < 400
)
gera_box_plot(ideal_fraction, 
              "Boxplot - ideal_fraction (fração ideal)",
              "ideal_fraction", 
              sufixo = "%", 
              escala = 100)

A variável ideal_fraction não apresenta outliers.

gera_box_plot(area_built_m2, 
              "Boxplot - area_built_m2 (área construída)",
              "area_built_m2", 
              sufixo = "k m2", 
              escala = 1e-03)

A variável area_built_m2 possui outliers. Decidiu-se excluir as observações com valores maiores que 10k m2. O código abaixo realiza o ajuste.

df2 <- df2 %>% filter(
  area_built_m2 < 10000
)
gera_box_plot(year_built, 
              "Boxplot - year_built (ano de construção)",
              "year_built", 
              sufixo = NULL, 
              escala = 1)

A variável year_built possui outliers, representados pelos valores iguais a zero, que podem ser explicados pelas propriedades com descrição land (terrenos), as quais não possuem edificação e, portanto, a variável year_built recebeu o valor zero. Assim, não é necessário remover estes outliers.

Tipagem de variáveis

Algumas variáveis no formato de caracter possuem características de serem categóricas, notadamente as variáveis district, zip_code, transaction_nature, date, mortgage_type, city_hall_status, description_1 e description_2. Nestes casos, essas variáveis são convertidas em fator (factor), a fim de serem usadas no modelo de regressão. A variável street_name não foi convertida pelo fato de que não será usada no modelo de regressão, em virtude de ser uma variável muito granular, com milhares de ruas diferentes. As variáveis district e zip_code serão usadas para tentar explicar o preço do imóvel em função de sua localização geográfica. Por esse mesmo motivo, também não serão convertidas as variáveis complement e reference.

Antes de realizar a transformação, verificou-se que a variável description_2 possui duas categorias que são iguais (horizontal_residence), mas que estão identificadas como diferentes apenas por um espaço em branco. O código abaixo remove o espaço em branco.

df2 <- df2 %>% mutate(
  description_2 = if_else(description_2 == "horizontal_residence ", "horizontal_residence", description_2)
)

A variável zip_code também possui o problema de ser muito granular, como a variável street_name. Para contornar esse problema, foram extraídos apenas os cinco primeiros dígitos do CEP. Isso permite agrupar os imóveis em torno de um CEP base em comum, que identifica uma região. A variável date não foi transformada em fator, pois entende-se que a mesma não tem efeito significativo na variável de interesse (transaction_value_BRL), pois a base de dados compreende o período de maio/22 a out/22, intervalo este muito curto para que a inflação tenha efeitos significativos no preço dos imóveis.

O código abaixo transforma algumas variáveis em factor. Na seção a seguir, é realizada a análise univariada das variáveis.

df2 <- df2 %>% mutate(
  district = as.factor(district),
  zip_code = as.factor(substr(zip_code, 1, 5)),
  transaction_nature = as.factor(transaction_nature),
  mortgage_type = as.factor(mortgage_type),
  city_hall_status = as.factor(city_hall_status),
  description_1 = as.factor(description_1),
  description_2 = as.factor(description_2)
)

Análise univariada

Nesta seção, será feita a análise univariadas das variáveis. O código abaixo cria os gráficos das distribuições das variáveis numéricas. A variável street_number não foi incluída nesta análise em razão de ser uma variável muito granular, pois há milhares de números diferentes, o que não ajuda a explicar o valor das transações. As variáveis registry_number e property_id também não foram incluídas por serem variáveis identificadoras.

# variáveis numéricas
var_num <- c("transaction_value_BRL", "cadastral_value", "tax_base_value", 
             "mortgage_value", "land_area_m2", "front_length_m", "ideal_fraction",
             "area_built_m2","year_built")

plots_num <- map(var_num, function(var) {
  df2 |>
    ggplot(aes(x = .data[[var]])) +
    geom_histogram(bins = 40, fill = "deepskyblue4", alpha = 0.75, color = "white") +
    scale_x_continuous(labels = comma) +
    labs(title = var, x = NULL, y = NULL) +
    theme_minimal(base_size = 9) +
    theme(plot.title = element_text(face = "bold", size = 9))
})

wrap_plots(plots_num, ncol = 3) +
  plot_annotation(
    title = "Distribuições — variáveis contínuas",
    caption = "Nota: escalas dos eixos-x variam entre os gráficos.",
    theme = theme(
      plot.title = element_text(face = "bold", size = 13),
      plot.caption = element_text(hjust = 0, size = 10, face = "italic")
      )
  )

A figura anterior mostra que todas variáveis numéricas possuem distribuições assimétricas, distantes do formato da distribuição “normal”.

A figura abaixo mostra as frequência das variáveis categóricas. Como algumas variáveis possuem muitas categorias possíveis, foram mostradas apenas as 10 primeiras categorias.

# variáveis categóricas
var_cat <- c("district", "zip_code", "transaction_nature", "mortgage_type",
             "city_hall_status", "description_1", "description_2")

plots_cat <- map(var_cat, function(var) {
  df2  %>% 
    count(.data[[var]], sort = TRUE)  %>% 
    slice_max(n, n = 10)  %>% 
    ggplot(aes(x = reorder(.data[[var]], n), y = n)) +
    geom_col(fill = "indianred4", alpha = 0.8, width = 0.7) +
    coord_flip() +
    labs(title = var, x = NULL, y = NULL) +
    theme_minimal(base_size = 8) +
    theme(plot.title = element_text(face = "bold", size = 8))
})

wrap_plots(plots_cat, ncol = 2) +
  plot_annotation(
    title = "Frequência — variáveis categóricas (10 primeiras categorias)",
    theme = theme(plot.title = element_text(face = "bold", size = 13))
  )

A figura anterior mostra que district e transaction_nature estão concentradas em apenas uma categoria (nao_informado e buy_sell), ao passo que as outras variáveis possuem um pouco mais de dispersão. A concentração em uma única categoria indica que district e transaction_nature não contribuem de forma significativa para definir o valor dos imóveis, em razão da baixa variabilidade.

Na próxima seção, é apresentada a análise bivariada das variáveis.

Análise bivariada

A variável de interesse é a transaction_value_BRL. Neste sentido, foram elaborados gráficos de dispersão entre a variável de interesse e a demais variáveis numéricas. O código abaixo gera os gráficos.

# criar uma tibble para definir o formato das variáveis
config_graficos <- tribble(
  ~var_graf_num,     ~prefixo_y, ~sufixo_y, ~escala_y,
  "cadastral_value", "R$ ",      "k",       1e-3,
  "tax_base_value",  "R$ ",      "k",       1e-3,
  "mortgage_value",  "R$ ",      "k",       1e-3,
  "land_area_m2",    "",         " m2",     1,
  "front_length_m",  "",         " m",      1,
  "ideal_fraction",  "",         "%",       100,
  "area_built_m2",   "",         " m2",     1,
  "year_built",      "",         "",        1
)

# gerar os gráficos
bivar_num <- pmap(config_graficos, function(var_graf_num, prefixo_y, sufixo_y, escala_y){
  df2 %>% 
    ggplot(aes(x = transaction_value_BRL, y = .data[[var_graf_num]])) +
    geom_point(alpha = 0.4, size = 1.5, colour = "blue4") + 
    geom_smooth(se = TRUE, 
                color = "#C04828", fill = "#C04828",
                alpha = 0.15, linewidth = 1) +
    scale_x_continuous(labels = dollar_format(prefix = "R$ ", scale = 1e-3,
                                              suffix = "k")) +
    scale_y_continuous(labels = dollar_format(prefix = prefixo_y, scale = escala_y,
                                              suffix = sufixo_y))
})
wrap_plots(bivar_num, ncol = 2) + 
  plot_annotation(
    title = "Gráfico de dispersão entre transaction_value_BRL e as demais variáveis numéricas",
    theme = theme(plot.title = element_text(face = "bold", size = 18))
  )

A figura anterior mostra que a relação entre transaction_value_BRL e as demais variáveis é, aproximadamente:

  • linear, com relação às variáveis tax_base_value, cadastral_value, land_area_m2, front_length_m, area_built_m2 e year_built; e

  • não linear, com relação às variáveis mortgage_value e ideal_fraction.

Para auxiliar na análise, o gráfico a seguir mostra as Correlações de Spearman entre todas as variáveis numéricas. A escolha do método de Spearman reside no fato de que os histogramas elaborados para as variáveis numéricas na análise univariada mostraram que todas as distribuições são assimétricas, ou seja, não “normais”. Além disso, há relações não lineares também entre duas variáveis (mortgage_value e ideal_fraction) e transaction_value_BRL, o que reforça a necessidade de uso do método citado. O código abaixo elabora o gráfico de correlação.

# criar a base de correlações entre todas as variáveis numéricas
base_corr <- df2 %>% select(all_of(var_num)) %>% 
  cor(use = "complete.obs", method="spearman")

# gráfico de correlação entre as variáveis numéricas
corrplot(base_corr,
         method    = "color",
         type      = "upper",
         order     = "hclust",
         tl.cex    = 0.85,
         tl.col    = "#1A1A18",
         addCoef.col = "#1A1A18",
         number.cex  = 0.72,
         col         = colorRampPalette(c("#C04828", "white", "#185FA5"))(200),
         diag        = FALSE,
         mar         = c(0, 0, 1, 0),
         title       = "Correlações de Pearson entre as variáveis numéricas")

O gráfico anterior mostra que transaction_value_BRL possui correlação positiva muito forte (acima de 0,90) com tax_base_value (0,93), correlação forte (acima de 0,50) com cadastral_value (0,54) e moderada (entre 0,30 e 0,50) com area_built_m2 (0,30). No tocante às demais variáveis, as correlações são reduzidas (menores que 0,30).

O figura a seguir compara a dispersão de transaction_value_BRL para diferentes variáveis categóricas. Como citado anteriormente, as variáveis district e transaction_nature possuem valores muito concentrados em uma única categoria, indicando que as mesmas talvez não sejam relevantes para explicar o preço dos imóveis. Isto posto, foi feita a comparação da dispersão de transaction_value_BRL com as categorias das variáveis zip_code, mortgage_type, city_hall_status, description_1 e description_2. Além disso, também foram escolhidas as 5 primeiras categorias de cada variável e o gráfico foi rotacionado, ambas as medidas para facilitar a visualização. O código abaixo elabora os gráficos.

# variáveis categóricas
var_cat_2 <- c("zip_code", "mortgage_type",
               "city_hall_status", "description_1", "description_2")

# gerar os gráficos
graficos_nun_cat <- map(var_cat_2, function(variavel){
  
  top_5_categorias <- df2 %>%
    count(.data[[variavel]], sort = TRUE) %>%
    slice_max(n, n = 5, with_ties = FALSE) %>%
    pull(.data[[variavel]])
  
  df2 %>%
      filter(.data[[variavel]] %in% top_5_categorias) %>%
    mutate(!!variavel := fct_reorder(as.factor(.data[[variavel]]), 
                                     transaction_value_BRL, 
                                     .fun = median)) %>%
    ggplot(aes(x = .data[[variavel]], y = transaction_value_BRL, 
               fill = .data[[variavel]])) +
    geom_boxplot(alpha = 0.75, outlier.alpha = 0.4,
                 outlier.size = 1.2, width = 0.55,
                 show.legend = FALSE) +
    scale_y_continuous(labels = dollar_format(prefix = "R$ ", 
                                              scale = 1e-3, suffix = "k")) +
    coord_flip() +
    labs(x = variavel, y = "Valor de Transação") + 
    scale_fill_brewer(palette = "Reds") +
    theme_minimal()
})

# gerar duas colunas de gráficos
wrap_plots(graficos_nun_cat, ncol = 2) + 
  plot_annotation(
    title = "Gráfico Boxplot de transaction_value_BRL por variáveis categóricas",
    theme = theme(plot.title = element_text(face = "bold", size = 18))
  )

A figura anterior mostra que não é possível, visualmente, avaliar se há diferença na mediana de transaction_value_BRL, em razão da existência de muitos outliers nessa variável. A figura a seguir altera os gráficos para não mostrar os outliers (mas mantém os mesmos na base de dados), bem como altera a escala do eixo-y, a fim de permitir melhor visualizar o Intervalo Interquartil.

# variáveis categóricas
var_cat_2 <- c("zip_code", "mortgage_type",
               "city_hall_status", "description_1", "description_2")

# gerar os gráficos
graficos_nun_cat <- map(var_cat_2, function(variavel){
  
  top_5_categorias <- df2 %>%
    count(.data[[variavel]], sort = TRUE) %>%
    slice_max(n, n = 5, with_ties = FALSE) %>%
    pull(.data[[variavel]])
  
  df2 %>%
      filter(.data[[variavel]] %in% top_5_categorias) %>%
    mutate(!!variavel := fct_reorder(as.factor(.data[[variavel]]), 
                                     transaction_value_BRL, 
                                     .fun = median)) %>%
    ggplot(aes(x = .data[[variavel]], y = transaction_value_BRL, 
               fill = .data[[variavel]])) +
    geom_boxplot(alpha = 0.75, outlier.alpha = 0.4,
                 outlier.size = 1.2, width = 0.55,
                 show.legend = FALSE,
                 outlier.shape = NA) +
    scale_y_continuous(labels = dollar_format(prefix = "R$ ", 
                                              scale = 1e-3, suffix = "k")) +
    coord_flip(ylim = c(0, 1500000)) +
    labs(x = variavel, y = "Valor de Transação") + 
    scale_fill_brewer(palette = "Reds") +
    theme_minimal()
})

# gerar duas colunas de gráficos
wrap_plots(graficos_nun_cat, ncol = 2) + 
  plot_annotation(
    title = "Gráfico Boxplot de transaction_value_BRL por variáveis categóricas (sem outliers)",
    theme = theme(plot.title = element_text(face = "bold", size = 18))
  )

A figura anterior mostra que há diferença de dispersão e de mediana de transaction_value_BRL para o primeiro zip_code (CEP) em relação aos outro quatro CEPs mostrados no gráfico. As demais variáveis também apresentam diferenças entre as categorias, principalmente em relação à dispersão dos dados.

Testes de hipótese

Nesta seção, são mostrados os resultados dos seguintes testes de hipótese: i) se há correlação entre o valor do imóvel e as demais variáveis numéricas; ii) se a localização do imóvel, indicado pelo CEP, influencia no seu preço; e iii) se o tipo de imóvel influencia no seu preço.

  • Teste da correlação entre o valor do imóvel e as demais variáveis

Este teste tem as seguintes hipóteses:

\(H_0\): não há correlação linear entre o valor do imóvel (transaction_value_BRL) e as demais variáveis numéricas.

\(H_1\): há correlação linear entre o valor do imóvel (transaction_value_BRL) e as demais variáveis numéricas.

Abaixo, é realizado o teste de correlação para avaliar a hipótese nula.

corr_var <- function(var, base){
  pearson = cor.test(base[[var]], base[["transaction_value_BRL"]], 
                     method="pearson", exact = F)
  spearman = cor.test(base[[var]], base[["transaction_value_BRL"]], 
                     method="spearman", exact = F)
  tibble(
    variavel = var,
    pearson_cor = round(pearson$estimate, 3),
    spearman_cor = round(spearman$estimate, 3),
    p_valor = round(pearson$p.value, 4),
    signif = case_when(
      pearson$p.value < 0.001 ~"***",
      pearson$p.value < 0.01 ~"**",
      pearson$p.value < 0.05 ~"*",
      TRUE ~ "ns"
    ),
    interpretacao = case_when(
      abs(pearson$estimate) >= 0.70 ~ "Forte",
      abs(pearson$estimate) >= 0.40 ~ "Moderada",
      abs(pearson$estimate) >= 0.20 ~ "Fraca",
      TRUE ~ "Desprezível"
    )
  )
}

# aplicar a funcao nas variaveis
tabela_cor <- c("cadastral_value", "tax_base_value", 
             "mortgage_value", "land_area_m2", "front_length_m", "ideal_fraction",
             "area_built_m2","year_built") %>% map_dfr(~corr_var(.x, base = df2))

# ordenar as colunas
tabela_cor <- tabela_cor %>% arrange(desc(pearson_cor))

# gerar a tabela
datatable(
  tabela_cor,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Teste de correlação entre transaction_value_BRL e as demais variáveis numéricas'),
  colnames = c(
    "Variável" = "variavel",
    "Cor. de Pearson" = "pearson_cor",
    "Cor. de Spearman" = "spearman_cor",
    "P-Valor" = "p_valor",
    "Significância" = "signif",
    "Interpretação" = "interpretacao"
  ),
  options = list(
    scrollX = TRUE,
    scrollY = "350px",
    pageLength = 10,
    dom = 't'
  ),
)


A correlação entre transaction_value_BRL e mortgage_value não foi significativa (p-valor > 0,05). Porém, a correlação de transaction_value_BRL com as demais variáveis foi significativa (p-valor < 0,001). Contudo, a correlação é forte apenas com as variáveis tax_base_value e cadastral_value.

Além disso, dentre as correlações fortes, a relação aparenta ser linear apenas entre transaction_value_BRL e tax_base_value, tendo em vista que os coeficientes de correlação de Pearson e de Spearman são próximos para esta variável. Já a correlação entre transaction_value_BRL e cadastral_value aparenta ser não linear, em razão da grande diferença entre os coeficientes de correlação de Pearson e de Spearman.

  • Teste para verificar se a localização do imóvel faz diferença no preço do imóvel

Na seção de Análise Bivariada, verificou-se que o zip_code (CEP) cujos primeiros cincos dígitos são 05409 possui mediana de preço maior que os demais CEPs. Neste sentido, será testada a hipótese que os imóveis cujo CEP começa com 05409 possuem preços (médios/medianos) maiores que aqueles localizados em outros CEPs. Formalmente, o teste apresenta as seguintes hipóteses:

\(H_0\): não há diferença de preço (médio/mediano) entre os imóveis localizados no CEP com início 05409 e os localizados nos demais CEPs.

\(H_1\): há diferença de preço (médio/mediano) entre os imóveis localizados no CEP com início 05409 e os localizados nos demais CEPs.

Antes do teste, é necessário verificar a normalidade dos dados. Para isso, será criada uma variável que indica se o imóvel pertence ao CEP 05409 ou não. O código abaixo cria essa nova variável e, em seguida, gera um gráfico de violino, e também boxplot, do preço dos imóveis por localização.

# gerar a variável
df2 <- df2 %>% mutate(
  is_05409 = if_else(as.character(zip_code) == "05409", "CEP_05409", "Demais_CEPs")
)

# gerar o gráfico
grafico_box <- function(base, variavel, titulo_princ, titulo_y){
  
  var_esc <- rlang::enquo(variavel)
  
  base %>% 
    ggplot(aes(y = !!var_esc, x = transaction_value_BRL, fill = !!var_esc)) +
    geom_violin(alpha = 0.6, show.legend = F) + 
    geom_boxplot(
      outlier.shape = NA,
      alpha=0.7,
      fill="white",
      show.legend = F
    ) +
    scale_x_continuous(
      labels = label_number(scale = 1e-03, suffix = "k", prefix = "R$ ",
                            big.mark = ".", decimal.mark = ",")
    ) +
    coord_cartesian(xlim = c(0, 1000000)) + 
    labs(
      title = titulo_princ,
      x = "Valor da transação",
      y = titulo_y
    ) + 
    theme_minimal() +
    theme(
      plot.title = element_text(face = "bold")
    )
}

grafico_box(df2, is_05409, "Distribuição de valores por localização", "Radical do CEP")

O gráfico acima mostra que as distribuições são assimétricas à esquerda, pois possuem uma cauda longa à direita. Além disso, a mediana do preço dos imóveis localizados no CEP com início 05409 é maior do que a mediana nos demais CEPs. Como forma de complementar a avaliação de “normalidade” dos dados, o código abaixo gera uma função para criar o qqplot e, em seguida, gera os gráficos.

# função para gerar o gráfico
grafico_qq <- function(base_dados, var_esc, local){
  
  variavel <- rlang::enquo(var_esc)
  
    plt <- base_dados %>% filter(is_05409 == local) %>% 
      ggplot(aes(sample = !!variavel)) +
      stat_qq(size = 2, alpha = 0.7) +
      stat_qq_line(color = "blue", linewidth = 1) +
      theme_minimal() +
      labs(
        title = paste0("QQ plot - ", rlang::as_name(variavel), " - ", local),
        x = "Quantis Teóricos (Normais)",
        y = "Quantis amostrais"
    ) +
      theme(
        plot.title = element_text(face="bold")
    )
    return(plt)
}

grafico1 <- df2 %>% grafico_qq(transaction_value_BRL, "CEP_05409")
grafico2 <- df2 %>% grafico_qq(transaction_value_BRL, "Demais_CEPs")
grafico1 / grafico2

Os gráficos qqplot comprovam que a variável transaction_value_BRL não segue distribuição “normal” para as propriedades no CEP 05409 e também nos demais CEPs. Para finalizar a avaliação da normalidade, foi realizado o teste de normalidade de Anderson-Darling. O teste de normalidade de Shapiro-Wilk não foi realizado pelo fato de ele ser limitado a amostras de até 5.000 observações. O resultado do teste é mostrado abaixo.

# função para calcular a normalidade por variável categórica
calc_normal <- function(base, variavel){
  
  var_esc <- rlang::enquo(variavel)
  
  resultados_normalidade <- base %>%
  group_by(!!var_esc) %>%
  summarise(
    n = n(),
    ad_estatistica = round(ad.test(transaction_value_BRL)$statistic, 2),
    p_valor = round(ad.test(transaction_value_BRL)$p.value, 2)
  ) %>%
  mutate(
    e_normal = if_else(p_valor > 0.05, "Sim", "Não")
  )
  return(resultados_normalidade)
}

# calcular o teste de normalidade por categoria de is_05409
res_normal_1 <- calc_normal(df2, is_05409)

# gerar a tabela
datatable(
  res_normal_1,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Teste de normalidade de Anderson-Darling para a variável transaction_value_BRL'),
  colnames = c(
    "Radical do CEP" = "is_05409",
    "Nr. de observações" = "n",
    "Valor da estatística" = "ad_estatistica",
    "P-Valor" = "p_valor",
    "É normal?" = "e_normal"
  ),
  rownames = NULL,
  options = list(
    scrollX = TRUE,
    scrollY = "100px",
    pageLength = 10,
    dom = 't'
  ),
) %>% 
  formatRound(columns = c("Valor da estatística","P-Valor"), digits = 2)


O resultado do teste de Anderson-Darling rejeita a hipótese de que a variável transaction_value_BRL segue distribuição “normal” tanto para os imóveis do CEP 05409 quanto para os demais.

Por fim, é realizado o Teste de Soma de Postos de Wilcoxon para comparar a mediana dos dois grupos. Este teste foi escolhido em razão da variável transaction_value_BRL não apresentar distribuição “normal”. O resultado é mostrado abaixo.

# configurar o gtsummary para o padrão brasileiro
theme_gtsummary_language(language = "pt", decimal.mark = ",", big.mark = ".")

# gerar a tabela com o teste
df2 %>% 
  select(is_05409, transaction_value_BRL) %>% 
  tbl_summary(
    by = is_05409
    ) %>% 
  add_p() %>% 
  modify_caption("**Teste de Soma de Postos de Wilcoxon**")
Teste de Soma de Postos de Wilcoxon
Características CEP_05409
N = 3741
Demais_CEPs
N = 76.1661
Valor-p2
transaction_value_BRL 400.000 (249.335 – 733.870) 300.000 (216.000 – 545.916) <0,001
1 Mediana (Q1 – Q3)
2 Teste de soma de postos de Wilcoxon

O resultado do teste rejeitou a hipótese nula de que a mediana do preço dos imóveis localizados nos CEPs iniciados em 05409 é igual à mediana de preço dos imóveis localizados em outros CEPs (p-valor < 0,001). Portanto, não rejeitamos a hipótese de que as medianas são diferentes.

Contudo, a diferença entre as medianas dos apartamentos e das casas é de R$ 100 mil (R$ 400 mil - R$ 300 mil). Para avaliar a relevância dessa diferença, foi calculada a Correlação de Rank-Biserial, conforme o código abaixo.

# calcular o rank biserial
res_rank <- rank_biserial(transaction_value_BRL ~ is_05409, data = df2)

# arredondar para duas casas
res_rank <- round(res_rank, 2)

# converter em tible
res_rank <- as_tibble(res_rank)
res_rank <- res_rank %>% select(-CI)

# gerar a tabela
res_rank %>%
  kable(
    align = 'c',
    col.names = c("Valor da estatística", "Limite inferior", "Limite superior"),
    caption = "Correlação de Rank-Biserial "
    ) %>%
  kable_styling(full_width = F) %>%
  add_header_above(c(" " = 1, "Intervalo de Confiança (95%)" = 2))
Correlação de Rank-Biserial
Intervalo de Confiança (95%)
Valor da estatística Limite inferior Limite superior
0,18 0,12 0,24


A tabela anterior mostra que o valor da Correlação de Rank-Biserial foi calculada em 0,18. Para interpretação do tamanho do efeito, foram utilizados os critérios propostos por Cohen (1988), nos quais valores entre 0,10 e 0,29 são considerados fracos. Portanto, apesar de não ser possível rejeitar a hipótese de que há diferença entre as medianas de preço dos imóveis localizados nos CEPs com início 05409 e dos demais imóveis, essa diferença é considerada fraca.

  • Teste para verificar se o tipo de imóvel influencia o seu preço

As hipóteses são:

\(H_0\): as distribuições do preço dos imóveis (transaction_value_BRL) são idênticas para todos os tipos de imóveis da variável description_1; e

\(H_1\): pelo menos um tipo de imóvel tem a distribuição de seus preços (transaction_value_BRL) diferente dos demais.

Em razão da base possuir 24 tipos diferentes de imóveis, representados pelas categorias da variável description_1, foram selecionadas apenas as 10 categorias com mais observações na base. O código abaixo realiza essa filtragem.

# filtrar os tipos de imóveis
tipos_desc <- df2 %>% 
  count(description_1, sort = T) %>% 
  slice_max(n, n = 10) %>%
  pull(description_1)

Em seguida, os tipos de imóveis escolhidos serão ordenados pela mediana da variável transaction_value_BRL, a fim de possibilitar a geração do gráfico de violino, junto com o boxplot, da variável numérica para os 10 tipos de imóveis selecionados. O código abaixo realiza este procedimento.

# ordenar os tipos de imoveis
bairros_ord <- df2 %>% 
  filter(description_1 %in% tipos_desc) %>% 
  mutate(
    description_1 = fct_reorder(description_1, transaction_value_BRL, 
                                .fun = median)
  )

# gerar o gráfico
grafico_box(bairros_ord, description_1, "Distribuição de valores por tipo de imóvel", "Tipo de imóvel")

O gráfico anterior mostra que há diferença nas medianas de transaction_value_BRL para os 10 tipos de imóveis escolhidos, com o maior valor para o tipo store (loja) e o menor para o tipo garage (garagem). Também há diferenças na dispersão dos dados, sendo que a loja possui maior dispersão e o tipo others (outros) aparenta ter a menor dispersão.

Em seguida, é realizado o teste de normalidade de Anderson-Darling para a variável transaction_value_BRL por tipo de imóvel. Como citado anterior, a escolha deste teste reside no fato de que ele não possui restrição de número de observações, ao contrário do teste de Shapiro-Wilk.

# realizar o teste de normalidade
res_normal_2 <- calc_normal(bairros_ord, description_1)

# gerar a tabela
datatable(
  res_normal_2,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Teste de normalidade de Anderson-Darling para a variável transaction_value_BRL'),
  colnames = c(
    "Tipo de imóvel" = "description_1",
    "Nr. de observações" = "n",
    "Valor da estatística" = "ad_estatistica",
    "P-Valor" = "p_valor",
    "É normal?" = "e_normal"
  ),
  rownames = NULL,
  options = list(
    scrollX = TRUE,
    scrollY = "450px",
    pageLength = 10,
    dom = 't'
  ),
)


O resultado do teste de Anderson-Darling rejeita a hipótese de que a variável transaction_value_BRL segue distribuição “normal” para os tipos de imóveis avaliados.

A próxima etapa consistiu em realizar o teste de Kruskal-Wallis para verificar se a distribuição do preço do imóvel é diferente sem razão do seu tipo. Este teste foi escolhido em razão da variável transaction_value_BRL não apresentar distribuição “normal” para nenhum tipo de imóvel. O código abaixo realiza o teste.

bairros_ord %>% 
  select(description_1, transaction_value_BRL) %>% 
  droplevels() %>% 
  tbl_summary(by = description_1) %>% 
  add_p()
Características garage
N = 4.8261
industry
N = 1.1201
land
N = 14.3451
others
N = 2.7371
hotel_flat
N = 8911
office
N = 3.1131
twinning_house
N = 2.4041
house
N = 13.9871
apartment
N = 28.6351
store
N = 1.9741
Valor-p2
transaction_value_BRL 55.000 (30.000 – 103.049) 251.004 (225.941 – 326.248) 264.000 (227.734 – 431.000) 264.000 (241.000 – 310.175) 265.000 (165.000 – 353.321) 292.000 (182.475 – 475.000) 300.000 (210.000 – 500.000) 350.000 (230.000 – 600.000) 371.655 (240.643 – 666.667) 405.000 (236.000 – 1.040.701) <0,001
1 Mediana (Q1 – Q3)
2 Teste de Kruskal-Wallis


O teste indicou a rejeição da hipótese nula de que as distribuições de preços são idênticas para todos os tipos de imóveis (p-valor < 0,001). Desta forma, há evidências estatísticas de que pelo menos um tipo de imóvel tem a distribuição de preços diferente dos demais.

Dado o resultado do teste de Kruskal-Wallis, aplicou-se o teste post-hoc de Dunn (com correção de Bonferroni) para realizar comparações múltiplas par a par. Esta etapa permitiu identificar quais tipos específicos de imóveis diferem entre si quanto ao valor do imóvel. O código abaixo realiza o teste.

# gerar a tabela com os resultados do teste
res_dunn <- bairros_ord %>% 
  dunn_test(transaction_value_BRL ~ description_1, 
            p.adjust.method = "bonferroni") %>% 
  select(group1, group2, statistic, p.adj, p.adj.signif) %>% 
  arrange(p.adj) %>% 
  mutate(
    statistic = round(statistic, 2),
    p.adj = round(p.adj, 2)
  )

# gerar a tabela
datatable(
  res_dunn,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Teste post-hoc de Dunn com correção de Bonferroni'),
  colnames = c(
    "Grupo 1" = "group1",
    "Grupo 2" = "group2",
    "Valor da estatística" = "statistic",
    "P-Valor ajustado" = "p.adj",
    "Significância" = "p.adj.signif"
  ),
  rownames = NULL,
  options = list(
    scrollX = TRUE,
    scrollY = "500px",
    pageLength = 10,
    dom = 'tp'
  ),
)


A tabela anterior demonstra que há evidências estatísticas para rejeitar a igualdade da distribuição de preços na maioria das comparações entre tipos de imóveis. Os pares que não apresentaram diferenças estatisticamente significativas entre si, ao nível de significância de 0,05, são listados abaixo:

  • industry vs. office (p-valor = 0,23);

  • industry vs. others (p-valor = 1,00);

  • land vs. office (p-valor = 1,00);

  • land vs. twinning_house (p-valor = 1,00); e

  • office vs. twinning_house (p-valor = 1,00).

Para avaliar a magnitude das diferenças encontradas, utilizou-se o coeficiente Eta quadrado (\(\eta^2\)). Esta medida indica a proporção da variância total da variável dependente (preço do imóvel) que é atribuída à variável independente (tipo de imóvel). Diferente do p-valor, que é influenciado pelo tamanho da amostra, o Eta quadrado permite quantificar a importância prática da associação encontrada. O código abaixo realiza o cálculo desta magnitude.

# calcular o efeito
magnit_df <- kruskal_effsize(transaction_value_BRL ~ description_1, 
                             data = bairros_ord)

# gerar a tabela
datatable(
  magnit_df,
  caption = htmltools::tags$caption(style = 'caption-side: top; text-align: center; color: black; font-weight: bold;', 'Tamanho do efeito do tipo do imóvel sobre o seu preço'),
  colnames = c(
    "Variável" = ".y.",
    "Nr. de observações" = "n",
    "Valor do efeito" = "effsize",
    "Método" = "method",
    "Magnitude" = "magnitude"
  ),
  rownames = NULL,
  options = list(
    scrollX = TRUE,
    scrollY = "100px",
    pageLength = 10,
    dom = 't'
  ),
) %>% 
  formatRound(columns = c("Valor do efeito"), digits = 2)


A tabela anterior mostra a magnitude da influência do tipo de imóvel sobre o seu preço é grande (large). Além disso, a tabela mostra que o tipo de imóvel explica cerca de 15% da variância do preço do imóvel.

Como há 24 tipos de imóveis, seria difícil criar um modelo com essa variável, em razão de possuir muitas categorias. O ideal seria agrupar os tipos de imóveis de acordo com algum critério, como as faixas de preços.

Para finalizar, se o intuito for elaborar um modelo, é necessário escolher quais as variáveis possuem maior chance de serem importantes para explicar/projetar o preço dos imóveis. Conforme as análises anteriores, as variáveis escolhidas foram:

  • tax_base_value. Motivo: apresentou correlação forte com transaction_value_BRL;

  • cadastral_value. Motivo: igual à anterior;

  • is_05409. Motivo: o teste mostrou evidências estatísticas de que os imóveis localizados no CEP com radical 05409 possuem valores (mediana) diferentes dos imóveis localizados em outros CEPs;

  • description_1. Motivo: o teste mostrou evidências estatísticas de que o tipo de imóvel influencia no seu preço; e

  • district. Motivo: apesar de não ter sido testado o seu efeito sobre o preço, é uma variável que, juntamente com o CEP, ajudam a identificar a localização do imóvel e, desta forma, pode ser uma variável útil no modelo. De forma análoga ao tipo de imóvel, talvez seja necessário agrupá-la, por possuir muitas categorias.

Com base nas variáveis escolhidas, será criada uma base de dados somentes com a variável de interesse (transaction_value_BRL) e as variáveis escolhidas anteriormente. O código abaixo realiza essa filtragem e salva os dados em um arquivo .RDS, o qual será utilizado no painel shiny descrito a seguir.

# gerar a base
df2_filtrada <- df2 %>% 
  select(transaction_value_BRL, tax_base_value, cadastral_value, is_05409,
         description_1, district)

# salvar a base em RDS
saveRDS(df2_filtrada, "df2_filtrada.rds")

Painel shiny

Foi criado um painel shiny, o qual permite criar gráficos de dispersão das variáveis numéricas selecionadas (tax_base_value e cadastral_value) com a variável de interesse (transaction_value_BRL). O painel está disponível neste endereço https://darthvaderbr44.shinyapps.io/pd_analise_exploratoria/

Referência bibliográfica

COHEN, J. Statistical Power Analysis for the Behavioral Sciences. 2. ed. Hillsdale: Lawrence Erlbaum Associates, 1988.