library(tidyverse)
library(skimr)
library(gt)
library(summarytools)
library(ggplot2)
library(corrplot)
library(patchwork)
library(rstatix)
library(gtsummary)
library(janitor)
library(naniar)
library(visdat)
library(DataExplorer)
library(scales)
library(knitr)
library(kableExtra)
library(tidymodels)
library(effectsize)
library(broom)Projeto de Disciplina 26.1
Análise Exploratória de Dados
1 Carregando os pacotes
2 Carregando os dados
Base de dados baixada do Kaggle: https://www.kaggle.com/datasets/allanbruno/airbnb-rio-de-janeiro
dez19 = read_csv("dados/dezembro2019.csv")
jan20 = read_csv("dados/janeiro2020.csv")
fev20 = read_csv("dados/fevereiro2020.csv")
mar20 = read_csv("dados/maro2020.csv")
abr20 = read_csv("dados/abril2020.csv")
maio20 = read_csv("dados/maio2020.csv")2.1 Descrição das bases de dados
Reúnem informações dos imóveis disponíveis para locação pelo Airbnb no Rio de Janeiro, do período de dezembro de 2019 até maio de 2020.
Possuem 108 colunas, que descrevem desde o preço e o período de disponibilidade de locação até detalhes sobre o host (dono) e do próprio imóvel.
O dicionário dos dados pode ser encontrado no seguinte link: https://docs.google.com/spreadsheets/d/1iWCNJcSutYqpULSQHlNyGInUvHg2BoUGoNRIGa6Szc4/edit?gid=1322284596#gid=1322284596
3 Objetivo da análise
O objetivo da presente análise consiste em identificar as variáveis que mais influenciaram o preço cobrado na locação de imóveis em anúncios do Airbnb no Rio de Janeiro no período disponível na base de dados.
A escolha por este tema de estudo se deu por uma curiosidade pessoal minha acerca da cidade onde moro desde nasci.
4 Limpeza dos dados
4.1 Construção de base única
Para facilitar o trabalho de limpeza e análise, as 6 bases serão unificadas em uma, com adição de uma coluna do mês e ano correspondente.
dez19 = dez19 |>
mutate(mes = "2019-12")
jan20 = jan20 |>
mutate(mes = "2020-01")
fev20 = fev20 |>
mutate(mes = "2020-02")
mar20 = mar20 |>
mutate(mes = "2020-03")
abr20 = abr20 |>
mutate(mes = "2020-04")
maio20 = maio20 |>
mutate(mes = "2020-05")df_raw = bind_rows(dez19, jan20, fev20, mar20, abr20, maio20)4.2 Visão geral
# skim(df_raw)Variável de interesse:
price, que determina o preço de aluguel dos imóveis.
A base df_raw possui muitas variáveis descartáveis para a análise, como listing_url, last_scraped, host_thumbnail_url, entre outras.
Sendo assim, abaixo serão selecionadas somente as variáveis que, de fato, podem ter alguma influência no preço do aluguel de um imóvel:
df_clean = df_raw |>
select(
price,
room_type,
accommodates,
bedrooms,
bathrooms,
neighbourhood_cleansed,
number_of_reviews,
review_scores_rating,
minimum_nights,
host_is_superhost,
property_type,
mes,
guests_included
)4.3 Investigação inicial
df_clean |>
select(where(is.numeric), where(is.factor)) |>
tbl_summary(
statistic = list(
all_continuous() ~ "{median} ({p25}, {p75})",
all_categorical() ~ "{n} ({p}%)"
),
digits = all_continuous() ~ 2
) |>
modify_header(label ~ "**Variável**") |>
modify_spanning_header(all_stat_cols() ~ "**Resumo Geral**") |>
modify_caption("**Tabela 1. Estatística descritiva das variáveis da base de dados**") |>
bold_labels()| Variável |
Resumo Geral
|
|---|---|
| N = 213,5141 | |
| accommodates | 4.00 (2.00, 5.00) |
| bedrooms | 1.00 (1.00, 2.00) |
| Unknown | 389 |
| bathrooms | 1.00 (1.00, 2.00) |
| Unknown | 374 |
| number_of_reviews | 1.00 (0.00, 6.00) |
| review_scores_rating | 98.00 (93.00, 100.00) |
| Unknown | 95,105 |
| minimum_nights | 2.00 (1.00, 4.00) |
| guests_included | 1.00 (1.00, 2.00) |
| 1 Median (Q1, Q3) | |
Há mais de 200 mil observações na nossa base. Nesta primeira descrição geral, destacam-se as seguintes medianas e IQRs de características dos imóveis colocados para locação pelo Airbnb no período de dezembro de 2019 até maio de 2020 no Rio de Janeiro:
Quantidade máxima de pessoas acomodadas (
accommodates): 4 (IQR = 2, 5)Quantidade de reviews (
number_of_reviews): 1 (IQR = 0, 6)Notas das reviews (
review_scores_rating): 98 (IQR = 93, 100)Quantidade mínima de noites a serem alugadas (
minimum_nights): 2 (IQR = 1, 4)
Preço (price) não apareceu na lista, o que indica que a variável deve estar erroneamente classificada como categórica.
4.3.1 Tipagem de variáveis
Identificando os tipos de variáveis presentes na base:
df_clean |>
summarise(across(everything(), class)) |>
pivot_longer(everything(),
names_to = "variavel",
values_to = "tipo") |>
count(tipo, sort = TRUE) |>
kbl(col.names = c("Tipo", "Quantidade"), align = "lr") |>
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE)| Tipo | Quantidade |
|---|---|
| numeric | 7 |
| character | 5 |
| logical | 1 |
A base possui o total de 13 variáveis, sendo 7 numéricas, 5 categóricas e 1 lógica.
Checando quais variáveis numéricas são, de fato, numéricas:
df_clean |>
select(where(is.numeric)) |>
glimpse()Rows: 213,514
Columns: 7
$ accommodates <dbl> 5, 2, 3, 2, 2, 13, 1, 10, 3, 6, 12, 2, 6, 4, 6, 4…
$ bedrooms <dbl> 2, 1, 1, 1, 1, 6, 1, 4, 1, 4, 4, 0, 4, 1, 3, 3, 0…
$ bathrooms <dbl> 1.0, 1.0, 1.0, 1.5, 1.0, 7.0, 1.0, 3.5, 1.0, 3.0,…
$ number_of_reviews <dbl> 246, 236, 274, 171, 320, 64, 42, 3, 79, 71, 7, 16…
$ review_scores_rating <dbl> 93, 94, 96, 94, 98, 92, 98, 80, 93, 91, 91, 95, 9…
$ minimum_nights <dbl> 4, 7, 2, 2, 3, 2, 3, 4, 3, 3, 1, 3, 7, 5, 2, 4, 3…
$ guests_included <dbl> 2, 2, 2, 2, 2, 7, 1, 8, 2, 4, 1, 2, 5, 2, 3, 2, 2…
review_scores_rating: variável de nível de qualidade percebida = ambíguo (pode ser tanto numérica quanto fator)bathrooms: variável que carrega informação dupla (quantidade de banheiros completos e lavabos) = será mantida como variável numérica única, pois as informações fazem sentido quando unificadas e facilitam a análiseacommodates,bedrooms,number_of_reviews,minimun_nightseguests_included: variáveis de contagem = numéricas inteiras
Tratando as variáveis:
df_clean = df_clean |>
mutate(
across(
c(accommodates, bedrooms, number_of_reviews,
minimum_nights, guests_included),
as.integer
)
)OBS: Optou-se por manter a variável review_scores_rating como numérica (dbl), por representar uma escala contínua de qualidade, preservando maior nível de informação.
Checando quais variáveis categóricas são, de fato, categóricas:
df_clean |>
select(where(is.character)) |>
glimpse()Rows: 213,514
Columns: 5
$ price <chr> "$323.00", "$159.00", "$266.00", "$368.00", "$1…
$ room_type <chr> "Entire home/apt", "Entire home/apt", "Entire h…
$ neighbourhood_cleansed <chr> "Copacabana", "Copacabana", "Ipanema", "Ipanema…
$ property_type <chr> "Condominium", "Apartment", "Apartment", "Apart…
$ mes <chr> "2019-12", "2019-12", "2019-12", "2019-12", "20…
price: variável de preço = numéricames: variável de mês = data
Tratando as variáveis:
# Tratando price
df_clean = df_clean |>
mutate(price = parse_number(price))# Tratando mes
df_clean = df_clean |>
mutate(mes = ymd(paste0(mes, "-01")))Verificando tipagem final das variáveis:
df_clean |>
glimpse()Rows: 213,514
Columns: 13
$ price <dbl> 323, 159, 266, 368, 131, 3492, 78, 851, 184, 90…
$ room_type <chr> "Entire home/apt", "Entire home/apt", "Entire h…
$ accommodates <int> 5, 2, 3, 2, 2, 13, 1, 10, 3, 6, 12, 2, 6, 4, 6,…
$ bedrooms <int> 2, 1, 1, 1, 1, 6, 1, 4, 1, 4, 4, 0, 4, 1, 3, 3,…
$ bathrooms <dbl> 1.0, 1.0, 1.0, 1.5, 1.0, 7.0, 1.0, 3.5, 1.0, 3.…
$ neighbourhood_cleansed <chr> "Copacabana", "Copacabana", "Ipanema", "Ipanema…
$ number_of_reviews <int> 246, 236, 274, 171, 320, 64, 42, 3, 79, 71, 7, …
$ review_scores_rating <dbl> 93, 94, 96, 94, 98, 92, 98, 80, 93, 91, 91, 95,…
$ minimum_nights <int> 4, 7, 2, 2, 3, 2, 3, 4, 3, 3, 1, 3, 7, 5, 2, 4,…
$ host_is_superhost <lgl> TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALS…
$ property_type <chr> "Condominium", "Apartment", "Apartment", "Apart…
$ mes <date> 2019-12-01, 2019-12-01, 2019-12-01, 2019-12-01…
$ guests_included <int> 2, 2, 2, 2, 2, 7, 1, 8, 2, 4, 1, 2, 5, 2, 3, 2,…
Agora possuímos 18 variáveis, sendo:
room_type,neighbourhood_cleansedeproperty_type: categóricasprice,accommodates,bedrooms,bathrooms,number_of_reviews,review_scores_rating,minimum_nights,guests_included: numéricashost_is_superhost: lógicames: data
Tratando a variável lógica (transformando em categórica para facilitar ações futuras):
df_clean = df_clean |>
mutate(
host_is_superhost = factor(host_is_superhost,
levels = c(TRUE, FALSE),
labels = c("Superhost", "Host"))
)Verificando tipagem final das variáveis:
df_clean |>
glimpse()Rows: 213,514
Columns: 13
$ price <dbl> 323, 159, 266, 368, 131, 3492, 78, 851, 184, 90…
$ room_type <chr> "Entire home/apt", "Entire home/apt", "Entire h…
$ accommodates <int> 5, 2, 3, 2, 2, 13, 1, 10, 3, 6, 12, 2, 6, 4, 6,…
$ bedrooms <int> 2, 1, 1, 1, 1, 6, 1, 4, 1, 4, 4, 0, 4, 1, 3, 3,…
$ bathrooms <dbl> 1.0, 1.0, 1.0, 1.5, 1.0, 7.0, 1.0, 3.5, 1.0, 3.…
$ neighbourhood_cleansed <chr> "Copacabana", "Copacabana", "Ipanema", "Ipanema…
$ number_of_reviews <int> 246, 236, 274, 171, 320, 64, 42, 3, 79, 71, 7, …
$ review_scores_rating <dbl> 93, 94, 96, 94, 98, 92, 98, 80, 93, 91, 91, 95,…
$ minimum_nights <int> 4, 7, 2, 2, 3, 2, 3, 4, 3, 3, 1, 3, 7, 5, 2, 4,…
$ host_is_superhost <fct> Superhost, Host, Superhost, Superhost, Superhos…
$ property_type <chr> "Condominium", "Apartment", "Apartment", "Apart…
$ mes <date> 2019-12-01, 2019-12-01, 2019-12-01, 2019-12-01…
$ guests_included <int> 2, 2, 2, 2, 2, 7, 1, 8, 2, 4, 1, 2, 5, 2, 3, 2,…
Agora host_is_superhost é fator.
4.3.2 Tratamento de NAs
Identificando a proporção de NAs:
na.summary = df_clean |>
summarise(across(everything(), ~ mean(is.na(.)))) |>
pivot_longer(everything(),
names_to = "variavel",
values_to = "pct_na") |>
filter(pct_na > 0) |>
arrange(desc(pct_na)) |>
mutate(pct_na_fmt = percent(pct_na, accuracy = 0.1))
na.summary |>
kbl(col.names = c("Variável", "% NA", "% Formatada"), align = "lrr", caption = "Variáveis com valores ausentes") |>
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE) |>
row_spec(which(na.summary$pct_na > 0.4), background = "#fa93b0")| Variável | % NA | % Formatada |
|---|---|---|
| review_scores_rating | 0.4454275 | 44.5% |
| bedrooms | 0.0018219 | 0.2% |
| bathrooms | 0.0017516 | 0.2% |
| host_is_superhost | 0.0006510 | 0.1% |
Visualização:
# Gerando amostragem para possibilitar a geração do gráfico
df_vis = df_clean |>
slice_sample(n = 5000)# Mapa Visual de NAs
vis_miss(
df_vis, sort_miss = TRUE
) +
labs(title = "Valores ausentes na base do Airbnb RJ",
subtitle = "Ordenado por proporção de missings") +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 7))Observa-se que review_scores_rating é a única variável com mais de 1% de NAs (44,5%).
Analisando cada caso de NAs:
review_scores_ratingNAs = não há reviews para o imóvelbedroomsebathroomsNAs = números não especificados no anúnciohost_is_superhostNAs = não especificado no anúncio
Tratando os NAs:
- Para
review_scores_rating,bedroomsebathrooms, foi imputada a mediana:
df_clean = df_clean |>
mutate(
# rating
review_scores_rating = coalesce(
review_scores_rating,
median(review_scores_rating, na.rm = TRUE)
),
# variáveis estruturais
across(
c(bedrooms, bathrooms),
~ coalesce(., median(., na.rm = TRUE))
)
)- Para
host_is_superhost, foi imputada a moda:
moda_superhost = names(sort(table(df_clean$host_is_superhost), decreasing = TRUE))[1]
moda_superhost[1] "Host"
df_clean = df_clean |>
mutate(
host_is_superhost = coalesce(host_is_superhost, moda_superhost)
)Checagem final da proporção de NAs da base de dados:
# Gerando amostragem para possibilitar a geração do gráfico
df_vis = df_clean |>
slice_sample(n = 5000)# Mapa Visual de NAs
vis_miss(
df_vis, sort_miss = TRUE
) +
labs(title = "Valores ausentes na base do Airbnb RJ",
subtitle = "Ordenado por proporção de missings") +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 7))NAs foram totalmente removidos da base de dados.
4.3.3 Tratamento de outliers
Identificando outliers:
df_clean |>
ggplot(aes(y = price)) +
geom_boxplot(outlier.alpha = 0.3) +
scale_y_continuous(
labels = scales::label_dollar(
prefix = "R$ ",
big.mark = ".",
decimal.mark = ","
)
) +
theme_minimal()Observa-se que há outliers especialmente a partir de mais de R$ 70.000. Há outliers que ultrapassam R$ 125.000.
Identificando o IQR
q1 = quantile(df_clean$price, 0.25)
q3 = quantile(df_clean$price, 0.75)
iqr = q3 - q1
inferior = q1 - 1.5 * iqr
superior = q3 + 1.5 * iqrCaracterísticas de price:
Q1 = R$ 157
Q3 = R$ 611
IQR = R$ 454
Inferior = - R$ 524
Superior = R$ 1.292
Identificando quantos imóveis são muito caros:
df_clean |>
filter(price > superior) |>
nrow()[1] 22297
Temos o total de 22.297 imóveis acima do IQR superior. São muitas observações para cortarmos.
Investigando os outliers por bairro:
top_bairros = df_clean |>
count(neighbourhood_cleansed, sort = TRUE) |>
slice_head(n = 15) |>
pull(neighbourhood_cleansed)
df_clean |>
filter(neighbourhood_cleansed %in% top_bairros) |>
ggplot(aes(x = neighbourhood_cleansed, y = price)) +
geom_boxplot() +
scale_y_continuous(
labels = scales::label_dollar(
prefix = "R$ ",
big.mark = ".",
decimal.mark = ","
)
) +
labs(
title = "Distribuição de preço por bairro",
subtitle = "Bairros selecionados: top 15",
x = "Bairro",
y = "Preço"
) +
coord_flip() +
theme_minimal()Observa-se que Copacana é o bairro com 2 observações de extrema discrepância em relação ao restante.
Investigando estes outliers:
df_clean |>
filter(neighbourhood_cleansed == "Copacabana") |>
arrange(desc(price)) |>
select(price, accommodates, bedrooms, bathrooms, room_type, property_type) |>
head(5) |>
gt()| price | accommodates | bedrooms | bathrooms | room_type | property_type |
|---|---|---|---|---|---|
| 138262 | 2 | 1 | 1 | Private room | Bed and breakfast |
| 131727 | 2 | 1 | 1 | Private room | Bed and breakfast |
| 57517 | 4 | 2 | 2 | Entire home/apt | Apartment |
| 55305 | 4 | 2 | 3 | Entire home/apt | Apartment |
| 54798 | 4 | 2 | 2 | Entire home/apt | Apartment |
Os dois valores mais altos (R$ 138.262 e R$ 131.727) são relativos a quartos privativos em propriedades de categoria “Bed and breakfast”, com acomodação para 2 pessoas, 1 banheiro e 1 quarto. Há fortes indicativos de erro, uma vez que nem mesmo em hotéis de luxo encontramos esses valores para quartos.
Logo, estes outliers serão removidos, pois também destoam muito da concentração geral de valores.
df = df_clean |>
filter(price <= 60000)Checagem final de outliers:
df |>
ggplot(aes(y = price)) +
geom_boxplot(outlier.alpha = 0.3) +
scale_y_continuous(
labels = scales::label_dollar(
prefix = "R$ ",
big.mark = ".",
decimal.mark = ","
)
) +
theme_minimal()Agora todas as observações concentram-se em valores até R$ 60.000.
5 Análise Univariada
5.1 Variáveis numéricas
Descrição estatística da variável numérica price, após remoção de outliers:
df |>
select(price) |>
descr(stats = c("mean", "sd", "min", "med", "max", "iqr"),
transpose = TRUE)Descriptive Statistics
df$price
N: 213510
Mean Std.Dev Min Median Max IQR
----------- -------- --------- ------ -------- ---------- --------
price 701.89 1930.32 0.00 298.00 57517.00 454.00
Trata-se de uma variável com mediana de R$ 298 e média de aproximadamente R$ 702, o que indica que pode se tratar de uma variável com assimetria à direita.
# Criando função para calcular normalidade
plot_normalidade = function(dados, var, bins = 30, xlim = NULL){
var_enquo = rlang::enquo(var)
vetor = dados |> dplyr::pull(!!var_enquo)
media = mean(vetor, na.rm = TRUE)
mediana = median(vetor, na.rm = TRUE)
p1 = dados |>
ggplot(aes(x = !!var_enquo)) +
geom_histogram(
aes(y = after_stat(density)),
bins = bins,
fill = "lightblue",
color = "black"
) +
geom_density(color = "darkblue", linewidth = 0.8, alpha = 0.3) +
theme_minimal() +
labs(
title = paste0("Distribuição - ", rlang::as_name(var_enquo)),
x = rlang::as_name(var_enquo),
y = "Densidade"
)
# aplicar limite do eixo x se for especificado
if(!is.null(xlim)){
p1 = p1 + coord_cartesian(xlim = xlim)
}
p2 = dados |>
ggplot(aes(sample = !!var_enquo)) +
stat_qq(size = 2, alpha = 0.6) +
stat_qq_line(color = "red", linewidth = 1) +
theme_minimal() +
labs(
title = paste0("QQ plot - ", rlang::as_name(var_enquo)),
x = "Quantis teóricos",
y = "Quantis amostrais"
)
p1 + p2
}df |>
plot_normalidade(price, bins = 300, xlim = c(0, 10000))OBS: A quantidade de bins escolhida para o histograma foi o valor encontrado que melhor permite a visualização da distribuição das observações.
Os gráficos acima indicam que, como esperado, a variável price não segue distribuição normal. Trata-se de uma variável assimétrica à direita. Ou seja, a maioria dos preços se concentram em valores abaixo de R$ 1.000, tendo poucas observações com valores extremos. O IQR de R$ 454 (R$ 157 - R$ 611) descreve a variabilidade dos preços mais comuns sem grande influência de valores extremos.
Descrição estatística da variável review_scores_rating:
df |>
select(review_scores_rating) |>
descr(stats = c("mean", "sd", "min", "med", "max", "iqr"),
transpose = TRUE)Descriptive Statistics
df$review_scores_rating
N: 213510
Mean Std.Dev Min Median Max IQR
-------------------------- ------- --------- ------- -------- -------- ------
review_scores_rating 96.11 7.28 20.00 98.00 100.00 2.00
Trata-se de uma variável com mediana de 98 e média de aproximadamente 96, o que indica que pode se tratar de uma variável com assimetria à esquerda.
df |>
plot_normalidade(review_scores_rating, bins = 100)OBS: A quantidade de bins escolhida para o histograma foi o valor encontrado que melhor permite a visualização da distribuição das observações.
Os gráficos acima indicam que, como esperado, a variável review_scores_rating não segue distribuição normal. Trata-se de uma variável assimétrica à esquerda. Ou seja, a maioria das notas se concentram em valores acima de 90, tendo poucas observações com notas baixas. O IQR de 2 (97, 99) comprova a baixa variabilidade de notas.
Descrição estatística da variável number_of_reviews:
df |>
select(number_of_reviews) |>
descr(stats = c("mean", "sd", "min", "med", "max", "iqr"),
transpose = TRUE)Descriptive Statistics
df$number_of_reviews
N: 213510
Mean Std.Dev Min Median Max IQR
----------------------- ------ --------- ------ -------- -------- ------
number_of_reviews 9.69 25.55 0.00 1.00 401.00 6.00
Trata-se de uma variável com mediana de 1 e média de aproximadamente 10, o que indica que pode se tratar de uma variável com assimetria à direita.
df |>
plot_normalidade(number_of_reviews, bins = 50, xlim = c(0, 100))OBS: A quantidade de bins escolhida para o histograma foi o valor encontrado que melhor permite a visualização da distribuição das observações.
Os gráficos acima indicam que, como esperado, a variável number_of_reviews não segue distribuição normal. Trata-se de uma variável assimétrica à direita. Ou seja, a maioria dos anúncios possuem menos de 1 review, sendo poucos os anúncios com maior quantidade de avaliações. O IQR de 6 (0, 6) descreve a variabilidade das quantidades de reviews por anúncio mais comuns sem grande influência de valores extremos.
5.2 Variáveis categóricas
Descrição estatística da variável room_type:
df |>
select(room_type) |>
tbl_summary() |>
modify_header(label ~ "**Variável**") |>
modify_spanning_header(all_stat_cols() ~ "**Proporção**") |>
modify_caption("**Tabela 3. Proporção de tipos de imóveis**") |>
bold_labels()| Variável |
Proporção
|
|---|---|
| N = 213,5101 | |
| room_type | |
| Entire home/apt | 153,028 (72%) |
| Hotel room | 1,278 (0.6%) |
| Private room | 54,591 (26%) |
| Shared room | 4,613 (2.2%) |
| 1 n (%) | |
df |>
filter(!is.na(room_type)) |>
ggplot(aes(x = reorder(room_type, room_type, function(x) length(x)), fill = room_type)) +
geom_bar() +
theme_minimal() +
labs (
x = "Tipo de imóvel",
y = "Frequência",
title = "Distribuição dos tipos de imóveis",
subtitle = "Base de dados: df_clean"
) +
theme(legend.position = "none")Observa-se que mais de 70% dos imóveis para locação são do tipo entire home/apt (casa/apartamento inteiro). A segunda categoria mais comum é private room (quarto privado). As demais categorias (quarto de hotel e quarto compartilhado) representam menos de 3% das observações, mesmo somadas.
Descrição estatística da variável neighbourhood_cleansed:
df |>
select(neighbourhood_cleansed) |>
mutate(neighbourhood_cleansed = fct_infreq(neighbourhood_cleansed)) |>
tbl_summary() |>
modify_header(label ~ "**Variável**") |>
modify_spanning_header(all_stat_cols() ~ "**Proporção**") |>
modify_caption("**Tabela 4. Proporção de bairros**") |>
bold_labels()| Variável |
Proporção
|
|---|---|
| N = 213,5101 | |
| neighbourhood_cleansed | |
| Copacabana | 55,910 (26%) |
| Barra da Tijuca | 23,989 (11%) |
| Ipanema | 18,385 (8.6%) |
| Jacarepaguá | 12,033 (5.6%) |
| Botafogo | 10,476 (4.9%) |
| Recreio dos Bandeirantes | 10,053 (4.7%) |
| Leblon | 9,764 (4.6%) |
| Santa Teresa | 6,920 (3.2%) |
| Centro | 5,922 (2.8%) |
| Flamengo | 5,518 (2.6%) |
| Tijuca | 4,344 (2.0%) |
| Laranjeiras | 4,278 (2.0%) |
| Leme | 3,722 (1.7%) |
| Lagoa | 2,416 (1.1%) |
| Glória | 2,125 (1.0%) |
| Catete | 2,034 (1.0%) |
| Jardim Botânico | 1,909 (0.9%) |
| Gávea | 1,851 (0.9%) |
| Humaitá | 1,777 (0.8%) |
| Camorim | 1,775 (0.8%) |
| Maracanã | 1,754 (0.8%) |
| Vila Isabel | 1,589 (0.7%) |
| São Conrado | 1,465 (0.7%) |
| Freguesia (Jacarepaguá) | 1,313 (0.6%) |
| Vargem Pequena | 1,149 (0.5%) |
| Taquara | 1,067 (0.5%) |
| Itanhangá | 1,028 (0.5%) |
| Vidigal | 964 (0.5%) |
| Urca | 875 (0.4%) |
| Vargem Grande | 764 (0.4%) |
| Cosme Velho | 693 (0.3%) |
| Rio Comprido | 681 (0.3%) |
| São Cristóvão | 631 (0.3%) |
| Grajaú | 618 (0.3%) |
| Estácio | 555 (0.3%) |
| Joá | 520 (0.2%) |
| Curicica | 516 (0.2%) |
| Andaraí | 504 (0.2%) |
| Campo Grande | 482 (0.2%) |
| Praça da Bandeira | 467 (0.2%) |
| Engenho de Dentro | 460 (0.2%) |
| Pechincha | 437 (0.2%) |
| Guaratiba | 416 (0.2%) |
| Barra de Guaratiba | 405 (0.2%) |
| Anil | 356 (0.2%) |
| Jardim Guanabara | 339 (0.2%) |
| Alto da Boa Vista | 336 (0.2%) |
| Méier | 330 (0.2%) |
| Engenho Novo | 307 (0.1%) |
| Todos os Santos | 242 (0.1%) |
| Cachambi | 225 (0.1%) |
| Irajá | 216 (0.1%) |
| Praça Seca | 206 (<0.1%) |
| São Francisco Xavier | 200 (<0.1%) |
| Paquetá | 188 (<0.1%) |
| Portuguesa | 177 (<0.1%) |
| Tanque | 162 (<0.1%) |
| Cidade Nova | 161 (<0.1%) |
| Saúde | 158 (<0.1%) |
| Del Castilho | 155 (<0.1%) |
| Riachuelo | 154 (<0.1%) |
| Gardênia Azul | 153 (<0.1%) |
| Rocha | 151 (<0.1%) |
| Brás de Pina | 149 (<0.1%) |
| Gamboa | 144 (<0.1%) |
| Bangu | 137 (<0.1%) |
| Rocinha | 134 (<0.1%) |
| Bonsucesso | 128 (<0.1%) |
| Santa Cruz | 128 (<0.1%) |
| Encantado | 126 (<0.1%) |
| Mangueira | 118 (<0.1%) |
| Penha | 116 (<0.1%) |
| Lins de Vasconcelos | 109 (<0.1%) |
| Santo Cristo | 104 (<0.1%) |
| Vila Valqueire | 96 (<0.1%) |
| Pedra de Guaratiba | 90 (<0.1%) |
| Piedade | 90 (<0.1%) |
| Tauá | 90 (<0.1%) |
| Maria da Graça | 83 (<0.1%) |
| Cidade de Deus | 82 (<0.1%) |
| Campinho | 79 (<0.1%) |
| Marechal Hermes | 79 (<0.1%) |
| Realengo | 78 (<0.1%) |
| Guadalupe | 77 (<0.1%) |
| Quintino Bocaiúva | 75 (<0.1%) |
| Jardim Sulacap | 73 (<0.1%) |
| Vila da Penha | 72 (<0.1%) |
| Cosmos | 68 (<0.1%) |
| Jardim Carioca | 68 (<0.1%) |
| Parque Anchieta | 68 (<0.1%) |
| Bento Ribeiro | 67 (<0.1%) |
| Madureira | 63 (<0.1%) |
| Padre Miguel | 63 (<0.1%) |
| Olaria | 61 (<0.1%) |
| Sampaio | 61 (<0.1%) |
| Higienópolis | 60 (<0.1%) |
| Ramos | 60 (<0.1%) |
| Vasco da Gama | 60 (<0.1%) |
| Benfica | 55 (<0.1%) |
| Cascadura | 55 (<0.1%) |
| Osvaldo Cruz | 54 (<0.1%) |
| Penha Circular | 54 (<0.1%) |
| Cacuia | 51 (<0.1%) |
| Catumbi | 51 (<0.1%) |
| Moneró | 51 (<0.1%) |
| Paciência | 51 (<0.1%) |
| Vicente de Carvalho | 50 (<0.1%) |
| Anchieta | 45 (<0.1%) |
| Inhaúma | 45 (<0.1%) |
| Pilares | 45 (<0.1%) |
| Santíssimo | 42 (<0.1%) |
| Engenho da Rainha | 41 (<0.1%) |
| Freguesia (Ilha) | 41 (<0.1%) |
| Tomás Coelho | 41 (<0.1%) |
| Senador Vasconcelos | 40 (<0.1%) |
| Sepetiba | 40 (<0.1%) |
| Pavuna | 39 (<0.1%) |
| Magalhães Bastos | 36 (<0.1%) |
| Jacaré | 34 (<0.1%) |
| Ricardo de Albuquerque | 34 (<0.1%) |
| Abolição | 33 (<0.1%) |
| Parada de Lucas | 33 (<0.1%) |
| Barros Filho | 32 (<0.1%) |
| Cocotá | 31 (<0.1%) |
| Galeão | 31 (<0.1%) |
| Senador Camará | 28 (<0.1%) |
| Cordovil | 26 (<0.1%) |
| Inhoaíba | 26 (<0.1%) |
| Água Santa | 24 (<0.1%) |
| Pitangueiras | 24 (<0.1%) |
| Praia da Bandeira | 22 (<0.1%) |
| Complexo do Alemão | 20 (<0.1%) |
| Vila Kosmos | 19 (<0.1%) |
| Bancários | 18 (<0.1%) |
| Grumari | 18 (<0.1%) |
| Rocha Miranda | 18 (<0.1%) |
| Coelho Neto | 16 (<0.1%) |
| Colégio | 16 (<0.1%) |
| Ribeira | 16 (<0.1%) |
| Vaz Lobo | 16 (<0.1%) |
| Vila Militar | 16 (<0.1%) |
| Vigário Geral | 14 (<0.1%) |
| Deodoro | 12 (<0.1%) |
| Manguinhos | 12 (<0.1%) |
| Engenheiro Leal | 10 (<0.1%) |
| Caju | 7 (<0.1%) |
| Honório Gurgel | 7 (<0.1%) |
| Vista Alegre | 7 (<0.1%) |
| Campo dos Afonsos | 6 (<0.1%) |
| Cavalcanti | 6 (<0.1%) |
| Gericinó | 6 (<0.1%) |
| Parque Colúmbia | 6 (<0.1%) |
| Jardim América | 5 (<0.1%) |
| Maré | 5 (<0.1%) |
| Cidade Universitária | 3 (<0.1%) |
| 1 n (%) | |
Observa-se que a partir do 16º bairro, a proporção de anúncios cai para menos de 1%. Por isso, eles serão cortados da visualização do gráfico abaixo:
df |>
filter(!is.na(neighbourhood_cleansed)) |>
count(neighbourhood_cleansed, sort = TRUE) |>
slice_head(n = 15) |>
ggplot(aes(
x = reorder(neighbourhood_cleansed, n),
y = n,
fill = neighbourhood_cleansed
)) +
geom_col() +
coord_flip() +
theme_minimal() +
labs(
x = "Bairro",
y = "Frequência",
title = "Distribuição dos bairros",
subtitle = "Top 15 bairros com mais anúncios"
) +
theme(legend.position = "none")Mais de 20% dos anúncios de imóveis para locação são em Copacabana. Leblon, o metro quadrado mais caro do Rio de Janeiro há alguns anos, é o 7º bairro com mais anúncios, ficando com menos de 5% deles. A maior concentração de bairros com anúncios fica na zona sul da cidade, seguida pela zona oeste e pelo Centro.
Descrição estatística da variável property_type:
df |>
select(property_type) |>
mutate(property_type = fct_infreq(property_type)) |>
tbl_summary() |>
modify_header(label ~ "**Variável**") |>
modify_spanning_header(all_stat_cols() ~ "**Proporção**") |>
modify_caption("**Tabela 5. Proporção de tipos de construção**") |>
bold_labels()| Variável |
Proporção
|
|---|---|
| N = 213,5101 | |
| property_type | |
| Apartment | 163,788 (77%) |
| House | 21,495 (10%) |
| Condominium | 12,563 (5.9%) |
| Loft | 4,186 (2.0%) |
| Serviced apartment | 3,986 (1.9%) |
| Guest suite | 1,323 (0.6%) |
| Bed and breakfast | 861 (0.4%) |
| Guesthouse | 812 (0.4%) |
| Villa | 660 (0.3%) |
| Hostel | 651 (0.3%) |
| Hotel | 621 (0.3%) |
| Other | 480 (0.2%) |
| Townhouse | 381 (0.2%) |
| Aparthotel | 242 (0.1%) |
| Cottage | 231 (0.1%) |
| Tiny house | 226 (0.1%) |
| Earth house | 183 (<0.1%) |
| Chalet | 181 (<0.1%) |
| Boutique hotel | 162 (<0.1%) |
| Boat | 120 (<0.1%) |
| Cabin | 60 (<0.1%) |
| Nature lodge | 57 (<0.1%) |
| Bungalow | 51 (<0.1%) |
| Casa particular (Cuba) | 42 (<0.1%) |
| Island | 29 (<0.1%) |
| Houseboat | 15 (<0.1%) |
| Castle | 12 (<0.1%) |
| Farm stay | 12 (<0.1%) |
| Tent | 12 (<0.1%) |
| Treehouse | 12 (<0.1%) |
| Campsite | 11 (<0.1%) |
| Dorm | 10 (<0.1%) |
| Hut | 10 (<0.1%) |
| Camper/RV | 8 (<0.1%) |
| Barn | 6 (<0.1%) |
| Yurt | 6 (<0.1%) |
| Vacation home | 4 (<0.1%) |
| Igloo | 1 (<0.1%) |
| 1 n (%) | |
Observa-se que a partir do 5º tipo, as categorias passam a representar menos de 1% do total. Logo, a visualização abaixo será focada no top 5:
df |>
filter(!is.na(property_type)) |>
count(property_type, sort = TRUE) |>
slice_head(n = 5) |>
ggplot(aes(
x = reorder(property_type, n),
y = n,
fill = property_type
)) +
geom_col() +
coord_flip() +
theme_minimal() +
labs(
x = "Tipo de construção",
y = "Frequência",
title = "Distribuição dos tipos de construção",
subtitle = "Top 5 tipos de construção mais anunciados"
) +
theme(legend.position = "none")Observa-se que mais de 70% dos imóveis para locação são do tipo Apartment. A segunda categoria mais comum é House, seguido por Condominium. As demais categorias (Loft e Serviced apartment) representam apenas cerca de 4% das observações, mesmo somadas.
6 Análise Bivariada e Testes de Hipóteses
A análise bivariada e os testes de hipóteses serão realizados com base nas 4 variáveis consideradas de maior potencial explicativo para a variável de interesse (preço de aluguel):
A variável de capacidade de hóspedes (
accommodates) foi escolhida para representar características estruturais do imóvel, permitindo avaliar a relação entre tamanho/capacidade e preço, o que possui interpretação econômica direta.A variável de status de superhost (
host_is_superhost) foi incluída por refletir aspectos de reputação do anfitrião, possibilitando investigar se fatores qualitativos influenciam o preço praticado.A variável de bairro (
neighbourhood_cleansed) foi selecionada por representar a localização do imóvel, um dos principais determinantes do valor no mercado imobiliário, permitindo analisar diferenças de preço entre regiões.Por fim, a variável de mês (
mes) foi utilizada para capturar possíveis efeitos sazonais ao longo do tempo, avaliando variações no preço associadas a períodos específicos (algo de extrema importância para turismo).
6.1 Price (numérica) x Categóricas
6.1.1 Price vs. neighbourhood_cleansed
Para analisar o impacto da localização no preço dos imóveis, foi realizada uma comparação entre os bairros com maior número de observações.
Hipóteses:
\(H_0\): Não há diferença de preço entre bairros
\(H_1\): Há diferença de preço entre pelo menos dois bairros
- Analisando os top 15 bairros:
df |>
filter(neighbourhood_cleansed %in% top_bairros) |>
mutate(neighbourhood_cleansed = fct_reorder(neighbourhood_cleansed, price, .fun = median)) |>
ggplot(aes(x = neighbourhood_cleansed, y = price, fill = neighbourhood_cleansed)) +
geom_boxplot(alpha = 0.75, outlier.alpha = 0.4, outlier.size = 1.2, width = 0.55, show.legend = FALSE) +
stat_summary(fun = mean, geom = "point", shape = 18, size = 3, color = "#C04828", show.legend = FALSE) +
scale_fill_manual(values = colorRampPalette(c("#B5D4F4", "#0C447C"))(15)) +
scale_y_continuous(labels = dollar_format(prefix = "R$")) +
coord_flip(ylim = c(0, 1000)) +
labs(title = "Preço de aluguel nos 15 bairros com mais anúncios",
subtitle = "Ordenado pela mediana | Losango vermelho = média",
x = "Bairro",
y = "Preço (Zoom até R$ 1000)") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank())df |>
filter(neighbourhood_cleansed %in% top_bairros) |>
group_by(neighbourhood_cleansed) |>
summarise(
n = n(),
min = min(price, na.rm = TRUE),
q1 = quantile(price, 0.25, na.rm = TRUE),
mediana = median(price, na.rm = TRUE),
q3 = quantile(price, 0.75, na.rm = TRUE),
media = mean(price, na.rm = TRUE),
max = max(price, na.rm = TRUE),
dp = sd(price, na.rm = TRUE)
) |>
arrange(desc(mediana)) |>
mutate(
across(c(min, q1, mediana, q3, media, max, dp),
~ dollar(., prefix = "R$"))
) |>
kbl(col.names = c("Bairro", "n", "Mínimo", "Q1", "Mediana", "Q3", "Média", "Máximo", "Desvio Padrão"),
align = "lrrrrrrrr") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)| Bairro | n | Mínimo | Q1 | Mediana | Q3 | Média | Máximo | Desvio Padrão |
|---|---|---|---|---|---|---|---|---|
| Lagoa | 2416 | R$49 | R$238.75 | R$498.50 | R$1,097.50 | R$1,018.11 | R$31,402 | R$2,025.27 |
| Leblon | 9764 | R$48 | R$288.00 | R$451.00 | R$852.00 | R$864.45 | R$25,002 | R$1,472.80 |
| Barra da Tijuca | 23989 | R$41 | R$247.00 | R$450.00 | R$1,002.00 | R$1,117.86 | R$54,981 | R$2,704.87 |
| Jacarepaguá | 12033 | R$0 | R$189.00 | R$413.00 | R$979.00 | R$801.42 | R$49,784 | R$2,203.05 |
| Ipanema | 18385 | R$30 | R$237.00 | R$399.00 | R$789.00 | R$790.46 | R$45,697 | R$1,729.51 |
| Recreio dos Bandeirantes | 10053 | R$28 | R$198.00 | R$393.00 | R$900.00 | R$796.92 | R$37,598 | R$1,516.67 |
| Leme | 3722 | R$38 | R$173.25 | R$298.00 | R$550.75 | R$604.23 | R$35,002 | R$1,842.64 |
| Copacabana | 55910 | R$0 | R$159.00 | R$250.00 | R$498.00 | R$562.04 | R$57,517 | R$1,580.41 |
| Botafogo | 10476 | R$37 | R$130.00 | R$220.00 | R$449.00 | R$435.19 | R$35,002 | R$1,276.05 |
| Flamengo | 5518 | R$35 | R$122.00 | R$218.00 | R$401.00 | R$485.68 | R$35,002 | R$1,445.48 |
| Laranjeiras | 4278 | R$37 | R$121.00 | R$218.00 | R$478.00 | R$480.65 | R$54,981 | R$1,862.18 |
| Tijuca | 4344 | R$31 | R$100.00 | R$201.00 | R$548.00 | R$500.00 | R$32,944 | R$1,251.79 |
| Santa Teresa | 6920 | R$35 | R$101.00 | R$179.00 | R$327.00 | R$550.04 | R$55,315 | R$2,328.70 |
| Glória | 2125 | R$29 | R$101.00 | R$176.00 | R$323.00 | R$433.42 | R$30,002 | R$1,762.49 |
| Centro | 5922 | R$30 | R$100.00 | R$151.00 | R$298.00 | R$425.81 | R$25,000 | R$1,306.30 |
Em primeira análise, pode-se supor que existe diferença entre os preços dos 15 bairros mais populares.
- Teste para confirmar a diferença de valor:
df_top = df |>
filter(neighbourhood_cleansed %in% top_bairros)
kruskal.test(price ~ neighbourhood_cleansed, data = df_top) |>
tidy() |>
gt()| statistic | p.value | parameter | method |
|---|---|---|---|
| 18016.2 | 0 | 14 | Kruskal-Wallis rank sum test |
A análise da relação entre o preço dos imóveis e o bairro por meio do teste de Kruskal-Wallis indicou diferença estatisticamente significativa entre os bairros (p < 0,05), levando à rejeição da hipótese nula (\(H_0\)) em favor da hipótese alternativa (\(H_1\)), ou seja, há evidências de que pelo menos dois bairros apresentam diferenças nos preços dos imóveis.
- Teste post hoc: analisar se os pares são diferentes estatisticamente
dunn_bairros = df_clean |>
filter(neighbourhood_cleansed %in% top_bairros) |>
dunn_test(price ~ neighbourhood_cleansed, p.adjust.method = "bonferroni") |>
filter(p.adj < 0.05) |>
select(group1, group2, statistic, p.adj) |>
mutate(
statistic = round(statistic, 2),
p.adj = round(p.adj, 4)
) |>
arrange(p.adj)dunn_bairros |>
kbl(col.names = c("Bairro 1", "Bairro 2", "Estatística Z", "p-valor ajustado"), caption = "Pares de bairros com diferença significativa (p Bonferroni < 0.05)", align = "llrr") |>
kable_styling(bootstrap_options = c("Stripped", "hover"), full_width = TRUE)| Bairro 1 | Bairro 2 | Estatística Z | p-valor ajustado |
|---|---|---|---|
| Barra da Tijuca | Botafogo | -61.10 | 0.0000 |
| Barra da Tijuca | Centro | -72.98 | 0.0000 |
| Barra da Tijuca | Copacabana | -70.61 | 0.0000 |
| Barra da Tijuca | Flamengo | -49.72 | 0.0000 |
| Barra da Tijuca | Glória | -42.77 | 0.0000 |
| Barra da Tijuca | Ipanema | -11.03 | 0.0000 |
| Barra da Tijuca | Jacarepaguá | -15.52 | 0.0000 |
| Barra da Tijuca | Laranjeiras | -41.97 | 0.0000 |
| Barra da Tijuca | Leme | -24.26 | 0.0000 |
| Barra da Tijuca | Recreio dos Bandeirantes | -14.97 | 0.0000 |
| Barra da Tijuca | Santa Teresa | -70.03 | 0.0000 |
| Barra da Tijuca | Tijuca | -44.91 | 0.0000 |
| Botafogo | Centro | -21.12 | 0.0000 |
| Botafogo | Copacabana | 16.02 | 0.0000 |
| Botafogo | Glória | -10.61 | 0.0000 |
| Botafogo | Ipanema | 49.63 | 0.0000 |
| Botafogo | Jacarepaguá | 40.57 | 0.0000 |
| Botafogo | Lagoa | 32.59 | 0.0000 |
| Botafogo | Leblon | 54.03 | 0.0000 |
| Botafogo | Leme | 15.10 | 0.0000 |
| Botafogo | Recreio dos Bandeirantes | 38.52 | 0.0000 |
| Botafogo | Santa Teresa | -15.49 | 0.0000 |
| Centro | Copacabana | 37.61 | 0.0000 |
| Centro | Flamengo | 16.92 | 0.0000 |
| Centro | Ipanema | 63.64 | 0.0000 |
| Centro | Jacarepaguá | 55.79 | 0.0000 |
| Centro | Lagoa | 44.70 | 0.0000 |
| Centro | Laranjeiras | 18.06 | 0.0000 |
| Centro | Leblon | 66.99 | 0.0000 |
| Centro | Leme | 30.19 | 0.0000 |
| Centro | Recreio dos Bandeirantes | 53.79 | 0.0000 |
| Centro | Santa Teresa | 5.84 | 0.0000 |
| Centro | Tijuca | 15.94 | 0.0000 |
| Copacabana | Flamengo | -13.98 | 0.0000 |
| Copacabana | Glória | -19.14 | 0.0000 |
| Copacabana | Ipanema | 51.39 | 0.0000 |
| Copacabana | Jacarepaguá | 36.98 | 0.0000 |
| Copacabana | Lagoa | 27.19 | 0.0000 |
| Copacabana | Laranjeiras | -9.55 | 0.0000 |
| Copacabana | Leblon | 53.75 | 0.0000 |
| Copacabana | Leme | 6.94 | 0.0000 |
| Copacabana | Recreio dos Bandeirantes | 33.89 | 0.0000 |
| Copacabana | Santa Teresa | -32.21 | 0.0000 |
| Copacabana | Tijuca | -12.42 | 0.0000 |
| Flamengo | Glória | -8.84 | 0.0000 |
| Flamengo | Ipanema | 41.32 | 0.0000 |
| Flamengo | Jacarepaguá | 34.99 | 0.0000 |
| Flamengo | Lagoa | 31.25 | 0.0000 |
| Flamengo | Leblon | 46.72 | 0.0000 |
| Flamengo | Leme | 14.84 | 0.0000 |
| Flamengo | Recreio dos Bandeirantes | 33.69 | 0.0000 |
| Flamengo | Santa Teresa | -11.81 | 0.0000 |
| Glória | Ipanema | 37.53 | 0.0000 |
| Glória | Jacarepaguá | 33.77 | 0.0000 |
| Glória | Lagoa | 33.22 | 0.0000 |
| Glória | Laranjeiras | 10.23 | 0.0000 |
| Glória | Leblon | 42.30 | 0.0000 |
| Glória | Leme | 19.88 | 0.0000 |
| Glória | Recreio dos Bandeirantes | 33.09 | 0.0000 |
| Glória | Tijuca | 8.59 | 0.0000 |
| Ipanema | Jacarepaguá | -5.57 | 0.0000 |
| Ipanema | Lagoa | 5.92 | 0.0000 |
| Ipanema | Laranjeiras | -34.66 | 0.0000 |
| Ipanema | Leblon | 12.19 | 0.0000 |
| Ipanema | Leme | -17.77 | 0.0000 |
| Ipanema | Recreio dos Bandeirantes | -5.62 | 0.0000 |
| Ipanema | Santa Teresa | -60.09 | 0.0000 |
| Ipanema | Tijuca | -37.49 | 0.0000 |
| Jacarepaguá | Lagoa | 8.68 | 0.0000 |
| Jacarepaguá | Laranjeiras | -29.39 | 0.0000 |
| Jacarepaguá | Leblon | 16.00 | 0.0000 |
| Jacarepaguá | Leme | -13.55 | 0.0000 |
| Jacarepaguá | Santa Teresa | -51.84 | 0.0000 |
| Jacarepaguá | Tijuca | -32.04 | 0.0000 |
| Lagoa | Laranjeiras | -28.16 | 0.0000 |
| Lagoa | Leme | -17.13 | 0.0000 |
| Lagoa | Recreio dos Bandeirantes | -8.73 | 0.0000 |
| Lagoa | Santa Teresa | -41.28 | 0.0000 |
| Lagoa | Tijuca | -29.97 | 0.0000 |
| Laranjeiras | Leblon | 40.41 | 0.0000 |
| Laranjeiras | Leme | 12.00 | 0.0000 |
| Laranjeiras | Recreio dos Bandeirantes | 28.41 | 0.0000 |
| Laranjeiras | Santa Teresa | -13.32 | 0.0000 |
| Leblon | Leme | -24.50 | 0.0000 |
| Leblon | Recreio dos Bandeirantes | -15.65 | 0.0000 |
| Leblon | Santa Teresa | -63.64 | 0.0000 |
| Leblon | Tijuca | -43.05 | 0.0000 |
| Leme | Recreio dos Bandeirantes | 13.01 | 0.0000 |
| Leme | Santa Teresa | -25.98 | 0.0000 |
| Leme | Tijuca | -14.02 | 0.0000 |
| Recreio dos Bandeirantes | Santa Teresa | -49.79 | 0.0000 |
| Recreio dos Bandeirantes | Tijuca | -30.99 | 0.0000 |
| Santa Teresa | Tijuca | 11.10 | 0.0000 |
| Barra da Tijuca | Leblon | 3.71 | 0.0221 |
| Centro | Glória | 3.60 | 0.0338 |
O teste de Dunn com ajuste de Bonferroni, para identificar quais pares de bairros diferem entre si, mostra diversas comparações com diferenças estatisticamente significativas, indicando que a variação de preços entre bairros é consistente e não se limita a casos isolados.
Tamanho do efeito: eta quadrado (primo do r)
0,01-<0.06 (pequeno),
0,06-<0,14 (moderado) e
0,14 (grande)
kruskal_effsize(price ~ neighbourhood_cleansed, data = df_top) |>
gt()| .y. | n | effsize | method | magnitude |
|---|---|---|---|---|
| price | 175855 | 0.1023783 | eta2[H] | moderate |
Adicionalmente, o tamanho de efeito estimado (0,10) foi classificado como moderado, sugerindo que, embora a localização tenha impacto relevante sobre os preços, ela não é o único fator determinante. Assim, conclui-se que o bairro exerce influência significativa sobre o preço dos imóveis, mas outros fatores também contribuem para sua variação.
df |>
filter(neighbourhood_cleansed %in% top_bairros) |>
select(neighbourhood_cleansed, price) |>
tbl_summary(by = neighbourhood_cleansed) |>
add_p()| Characteristic | Barra da Tijuca N = 23,9891 |
Botafogo N = 10,4761 |
Centro N = 5,9221 |
Copacabana N = 55,9101 |
Flamengo N = 5,5181 |
Glória N = 2,1251 |
Ipanema N = 18,3851 |
Jacarepaguá N = 12,0331 |
Lagoa N = 2,4161 |
Laranjeiras N = 4,2781 |
Leblon N = 9,7641 |
Leme N = 3,7221 |
Recreio dos Bandeirantes N = 10,0531 |
Santa Teresa N = 6,9201 |
Tijuca N = 4,3441 |
p-value2 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| price | 450 (247, 1,002) | 220 (130, 449) | 151 (100, 298) | 250 (159, 498) | 218 (122, 401) | 176 (101, 323) | 399 (237, 789) | 413 (189, 979) | 499 (239, 1,098) | 218 (121, 478) | 451 (288, 852) | 298 (173, 551) | 393 (198, 900) | 179 (101, 327) | 201 (100, 548) | <0.001 |
| 1 Median (Q1, Q3) | ||||||||||||||||
| 2 Kruskal-Wallis rank sum test | ||||||||||||||||
6.1.2 Price vs. mes
Para investigar possíveis efeitos sazonais no preço dos imóveis, foi realizada uma análise bivariada entre o preço e o mês de referência.
Hipóteses:
\(H_0\): Não há diferença de preço entre os meses
\(H_1\): Há diferença de preço entre pelo menos dois meses
- Transformando a variável em categórica para que possamos realizar as análises necessárias:
df = df |>
mutate(mes_cat = factor(format(mes, "%Y-%m"), levels = sort(unique(format(mes, "%Y-%m")))))- Analisando os meses presentes na base:
df |>
ggplot(aes(x = mes_cat, y = price, fill = mes_cat)) +
geom_boxplot(alpha = 0.75, outlier.alpha = 0.4, outlier.size = 1.2, width = 0.55, show.legend = FALSE) +
stat_summary(fun = mean, geom = "point", shape = 18, size = 3, color = "#C04828", show.legend = FALSE) +
scale_fill_manual(values = colorRampPalette(c("#B5D4F4", "#0C447C"))(6)) +
scale_y_continuous(labels = dollar_format(prefix = "R$")) +
coord_flip(ylim = c(0, 1000)) +
labs(title = "Preço de aluguel ao longo dos meses",
subtitle = "Ordenado pela mediana | Losango vermelho = média",
x = "Mês",
y = "Preço") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank())df |>
group_by(mes_cat) |>
summarise(
n = n(),
min = min(price, na.rm = TRUE),
q1 = quantile(price, 0.25, na.rm = TRUE),
mediana = median(price, na.rm = TRUE),
q3 = quantile(price, 0.75, na.rm = TRUE),
media = mean(price, na.rm = TRUE),
max = max(price, na.rm = TRUE),
dp = sd(price, na.rm = TRUE)
) |>
arrange(desc(mediana)) |>
mutate(
across(c(min, q1, mediana, q3, media, max, dp),
~ dollar(., prefix = "R$"))
) |>
kbl(col.names = c("Mês", "n", "Mínimo", "Q1", "Mediana", "Q3", "Média", "Máximo", "Desvio Padrão"),
align = "lrrrrrrrr") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)| Mês | n | Mínimo | Q1 | Mediana | Q3 | Média | Máximo | Desvio Padrão |
|---|---|---|---|---|---|---|---|---|
| 2019-12 | 34551 | R$0 | R$155 | R$299 | R$601 | R$655.39 | R$42,940 | R$1,703.17 |
| 2020-03 | 36085 | R$0 | R$157 | R$299 | R$599 | R$683.30 | R$52,763 | R$1,922.40 |
| 2020-05 | 35791 | R$0 | R$159 | R$299 | R$649 | R$787.94 | R$57,517 | R$2,227.33 |
| 2020-01 | 34754 | R$0 | R$155 | R$298 | R$600 | R$645.69 | R$41,966 | R$1,674.25 |
| 2020-02 | 36460 | R$0 | R$158 | R$298 | R$632 | R$686.07 | R$46,085 | R$1,854.19 |
| 2020-04 | 35869 | R$0 | R$158 | R$298 | R$632 | R$750.03 | R$55,296 | R$2,119.42 |
Em primeira análise, pode-se afirmar que as medianas permanecem constantes ao longo do período analisado, com pequenas diferenças na dispersão. No entanto, as médias apresentam variações, sugerindo a presença de valores extremos em determinados períodos. Isso indica que, apesar do preço típico dos imóveis se manter estável, alguns meses concentram imóveis com preços mais elevados, aumentando a média.
- Teste para confirmar a diferença de valor:
kruskal.test(price ~ mes_cat, data = df) |>
tidy() |>
gt()| statistic | p.value | parameter | method |
|---|---|---|---|
| 21.74215 | 0.0005861063 | 5 | Kruskal-Wallis rank sum test |
A análise da relação entre o preço dos imóveis e o bairro por meio do teste de Kruskal-Wallis indicou diferença estatisticamente significativa entre os bairros (p < 0,05). Não é possível afirmar qual hipótese é a vencedora ainda, uma vez que o resultado do teste contraria o observado em primeira análise.
- Teste post hoc: analisar se os pares são diferentes estatisticamente (mostrando somente os pares que são)
dunn_mes = df |>
dunn_test(price ~ mes_cat, p.adjust.method = "bonferroni") |>
filter(p.adj < 0.05) |>
select(group1, group2, statistic, p.adj) |>
mutate(
statistic = round(statistic, 2),
p.adj = round(p.adj, 4)
) |>
arrange(p.adj)dunn_mes |>
kbl(col.names = c("Mês 1", "Mês 2", "Estatística Z", "p-valor ajustado"), caption = "Pares de bairros com diferença significativa (p Bonferroni < 0.05)", align = "llrr") |>
kable_styling(bootstrap_options = c("Stripped", "hover"), full_width = TRUE)| Mês 1 | Mês 2 | Estatística Z | p-valor ajustado |
|---|---|---|---|
| 2020-01 | 2020-05 | 3.81 | 0.0021 |
| 2019-12 | 2020-05 | 3.15 | 0.0245 |
| 2020-01 | 2020-04 | 3.14 | 0.0257 |
O teste de Dunn com ajuste de Bonferroni identificou diferenças estatisticamente significativas entre alguns pares de meses. Em particular, observou-se que janeiro apresenta preços significativamente mais elevados em comparação a maio e abril, enquanto dezembro também apresenta preços superiores aos de maio. Esses resultados indicam que as diferenças ao longo do tempo são pontuais e concentradas em determinados meses, não caracterizando um padrão sazonal uniforme.
Tamanho do efeito: eta quadrado (primo do r)
0,01-<0.06 (pequeno),
0,06-<0,14 (moderado) e
0,14 (grande)
kruskal_effsize(price ~ mes_cat, data = df) |>
gt()| .y. | n | effsize | method | magnitude |
|---|---|---|---|---|
| price | 213510 | 7.84161e-05 | eta2[H] | small |
Embora o teste de Kruskal-Wallis tenha indicado diferença estatisticamente significativa entre os meses (p < 0,05), o tamanho de efeito estimado foi extremamente baixo (0,00008), sendo classificado como pequeno. Isso indica ausência de impacto relevante do período sobre os preços no período analisado.
Ressalta-se, contudo, que a análise foi realizada com base em um subconjunto temporal limitado (6 meses), o que pode restringir a identificação de padrões sazonais mais amplos.
df |>
mutate(mes_cat = factor(format(mes, "%Y-%m"), levels = sort(unique(format(mes, "%Y-%m")))))|>
select(mes_cat, price) |>
tbl_summary(by = mes_cat) |>
add_p()| Characteristic | 2019-12 N = 34,5511 |
2020-01 N = 34,7541 |
2020-02 N = 36,4601 |
2020-03 N = 36,0851 |
2020-04 N = 35,8691 |
2020-05 N = 35,7911 |
p-value2 |
|---|---|---|---|---|---|---|---|
| price | 299 (155, 601) | 298 (155, 600) | 298 (158, 632) | 299 (157, 599) | 298 (158, 632) | 299 (159, 649) | <0.001 |
| 1 Median (Q1, Q3) | |||||||
| 2 Kruskal-Wallis rank sum test | |||||||
6.1.3 Price vs. host_is_superhost
Para analisar se anfitriões superhosts possuem preços diferentes dos demais, foi realizada uma comparação entre os dois grupos.
Hipóteses:
\(H_0\): Não há diferença de preço entre hosts e superhosts
\(H_1\): Há diferença de preço entre hosts e superhosts
- Analisando os dados:
df |>
ggplot(aes(y = host_is_superhost, x = price, fill = host_is_superhost)) +
geom_violin(alpha = 0.5, color = NA, show.legend = FALSE) +
geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white", alpha = 0.8, show.legend = FALSE) +
stat_summary(fun = mean, geom = "point", shape = 18, size = 3.5, color = "#C04824", show.legend = FALSE) +
scale_x_continuous(labels = scales::dollar_format(prefix = "R$")) +
coord_cartesian(xlim = c(0, 1000)) +
labs(
x = "Preço",
y = "Tipo de anfitrião",
title = "Distribuição dos preços por tipo de anfitrião",
subtitle = "Zoom até R$1000 | Losango vermelho = média"
) +
theme_minimal()df |>
group_by(host_is_superhost) |>
summarise(
n = n(),
min = min(price, na.rm = TRUE),
mediana = median(price, na.rm = TRUE),
q1 = quantile(price, 0.25),
q3 = quantile(price, 0.75),
media = mean(price, na.rm = TRUE),
max = max(price, na.rm = TRUE),
dp = sd(price)
) |>
arrange(desc(mediana)) |>
mutate(
across(c(min, max, mediana, media, dp, q1, q3),
~ dollar(., prefix = "R$"))
) |>
kbl(col.names = c("Anfitrião", "n", "Mínimo", "Mediana", "Q1", "Q3", "Média", "Máximo", "Desvio Padrão"),
align = "lrrrrrrrr") |>
kable_styling(bootstrap_options = c("stripped", "hover"), full_width = TRUE)| Anfitrião | n | Mínimo | Mediana | Q1 | Q3 | Média | Máximo | Desvio Padrão |
|---|---|---|---|---|---|---|---|---|
| Host | 186373 | R$0 | R$299 | R$166 | R$684 | R$726.15 | R$57,517 | R$1,951.05 |
| Superhost | 27137 | R$29 | R$202 | R$131 | R$361 | R$535.28 | R$46,085 | R$1,772.52 |
Em primeira análise, pode-se supor que os superhosts cobram menos (em média) do que os hosts comuns. A mediana de preço de hosts comuns (R$ 299, IQR = R$ 166-684) é cerca de 32% maior do que de superhosts (R$ 202, IQR = R$ 131-361). Também observa-se uma maior variabilidade de preço entre os hosts comuns.
- Teste de normalidade da variável:
shapiro.test(sample(df$price, min(nrow(df), 5000))) |>
tidy() |>
gt()| statistic | p.value | method |
|---|---|---|
| 0.2648087 | 1.047293e-88 | Shapiro-Wilk normality test |
OBS: Foi necessário fazer uma amostragem da base de dados, uma vez que o teste Shapiro-Wilk suporta no máximo 5000 observações.
Confirma-se que a variável price (preço) não é normal, como observado na análise univariada.
- Tabela por grupo:
df |>
group_by(host_is_superhost) |>
summarise(
n = n(),
mediana = scales::dollar(median(price, na.rm = TRUE)),
media = scales::dollar(mean(price, na.rm = TRUE)),
dp = scales::dollar(sd(price, na.rm = TRUE)),
q1 = scales::dollar(quantile(price, 0.25, na.rm = TRUE)),
p_shapiro = shapiro.test(sample(price, min(n(), 4999)))$p.value
) |>
kbl(
col.names = c("Grupo", "N", "Mediana", "Média", "DP", "Q1", "p-valor (Shapiro)"),
align = "lrrrrrr"
) |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)| Grupo | N | Mediana | Média | DP | Q1 | p-valor (Shapiro) |
|---|---|---|---|---|---|---|
| Host | 186373 | $299 | $726.15 | $1,951.05 | $166 | 0 |
| Superhost | 27137 | $202 | $535.28 | $1,772.52 | $131 | 0 |
- Testando as hipóteses:
wilcox.test(
price ~ host_is_superhost,
data = df
) |>
tidy() |>
gt()| statistic | p.value | method | alternative |
|---|---|---|---|
| 3103251280 | 0 | Wilcoxon rank sum test with continuity correction | two.sided |
O teste de Wilcoxon indicou diferença estatisticamente significativa entre os preços de imóveis de superhosts e hosts comuns (p < 0,001), levando à suspeita de possível rejeição da hipótese nula.
Esse resultado sugere que o status de superhost está associado a variações nos preços dos imóveis.
- Testando o tamanho do efeito:
rank_biserial(price ~ host_is_superhost, data = df) |>
tidy()# A tibble: 4 × 13
column n mean sd median trimmed mad min max range skew kurtosis
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 r_ran… 1 0.227 NA 0.227 0.227 0 0.227 0.227 0 NaN NaN
2 CI 1 0.95 NA 0.95 0.95 0 0.95 0.95 0 NaN NaN
3 CI_low 1 0.220 NA 0.220 0.220 0 0.220 0.220 0 NaN NaN
4 CI_hi… 1 0.234 NA 0.234 0.234 0 0.234 0.234 0 NaN NaN
# ℹ 1 more variable: se <dbl>
levels(as.factor(df$host_is_superhost))[1] "Host" "Superhost"
O coeficiente rank biserial (r = 0,23) indicou um efeito de magnitude pequena a moderada, sugerindo que imóveis de hosts comuns tendem a apresentar preços mais elevados do que aqueles de superhosts. Apesar da significância estatística observada, a magnitude do efeito indica que essa diferença, embora consistente, não é substancial em termos práticos. Logo, a hipótese nula não foi rejeitada.
6.2 Price (numérica) x Numérica
6.2.1 Price vs. accommodates
Para analisar se imóveis com maior capacidade de hóspedes apresentam preços mais elevados, foi avaliada a relação entre o número de pessoas acomodadas e o preço.
Hipóteses:
\(H_0\): Não há correlação entre o número de hóspedes acomodados e o preço (r = 0)
\(H_1\): Há correlação entre o número de hóspedes acomodados e o preço (r > 0)
- Analisando os dados:
p1 = df |>
ggplot(aes(x = accommodates, y = price)) +
geom_point(alpha = 0.25, color = "#185FA5", size = 1.2) +
geom_smooth(method = "lm", se = FALSE, color = "#C04824", linewidth = 1, linetype = "solid") +
scale_x_continuous(labels = comma) +
scale_y_continuous(labels = scales::dollar_format(prefix = "R$")) +
labs(title = "Capacidade de hóspedes x Preço",
subtitle = "Vermelho = linear",
x = "Número de hóspedes (accommodates)",
y = "Preço") +
theme_minimal() +
theme(plot.title = element_text(face = "bold"))
p1cor(df$accommodates, df$price, method = "pearson")[1] 0.2464007
cor(df$accommodates, df$price, method = "spearman")[1] 0.5191272
Segundo o coeficiente de Spearman, há uma correlação moderada (r = 0,51) entre o preço de aluguel e a quantidade de pessoas que o imóvel acomoda. Como observado pelo gráfico, trata-se de uma relação monotônica, não-linear. Por isso, optou-se pelo uso do coeficiente de Spearman para descrever sua força.
7 Conclusão
A análise exploratória da variável preço (price) evidenciou uma distribuição fortemente assimétrica à direita, com presença de outliers expressivos, indicando que poucos imóveis com valores muito elevados impactam significativamente a distribuição. Esse comportamento reforça a adequação do uso de medidas robustas, como mediana e intervalo interquartil, ao longo da análise.
Na análise bivariada, observou-se que o número de hóspedes que o imóvel comporta (accommodates) apresenta associação positiva com o preço, indicando que imóveis com maior capacidade tendem a ser mais caros. No entanto, o scatterplot evidenciou que essa relação não é estritamente linear, sugerindo que o aumento do preço não ocorre de forma constante conforme a capacidade cresce.
Além disso, a análise por bairros revelou diferenças relevantes nos preços dos imóveis, indicando variações espaciais importantes dentro da cidade do Rio de Janeiro. Essas diferenças refletem a heterogeneidade do mercado imobiliário local, em que determinadas regiões concentram imóveis com valores mais elevados.
De forma geral, os resultados mostram que o preço dos imóveis no Airbnb é influenciado principalmente pela capacidade de acomodação e pela localização, evidenciando que tanto características quantitativas quanto fatores espaciais desempenham papel central na definição dos valores.
8 App Shiny
- Salvando a base de dados final:
df_modelo = df |>
select(price, neighbourhood_cleansed, mes_cat, host_is_superhost, accommodates)
saveRDS(df_modelo, "df_modelo.rds")Link para o app: https://9ttrpl-anabella-sgarbi0pereira.shinyapps.io/PD_EDA/
9 RPubs
Link para este projeto no RPubs: https://rpubs.com/ana_sgarbi/pd_analise_exploratoria_de_dados