Faça uma análise exploratória para avaliar a consistência dos dados e identifcar possíveis variáveis que impactam sua variável resposta.
Para a realização deste teste você pode utilizar o software de sua preferência (Python ou R).
Sua solução deverá ser entregue no formato Jupyter, por meio de um repositório Git. Inclua também um arquivo README.md no qual você deve cobrir as respostas para os 5 pontos abaixo.
a. Como foi a definição da sua estratégia de modelagem?
b. Como foi definida a função de custo utilizada?
c. Qual foi o critério utilizado na seleção do modelo final?
d. Qual foi o critério utilizado para validação do modelo? Por que escolheu utilizar esse método?
e. Quais evidências você possui de que seu modelo é suficientemente bom?
O dataset utilizado é uma extração de dados do Airbnb Rio de Janeiro, conforme
fonte.

Airbnb permite aos indivíduos alugar o todo ou parte de sua própria casa, como uma forma de acomodação extra. O site fornece uma plataforma de busca e reservas entre a pessoa que oferece a acomodação e o turista que busca pela locação. Abrange mais de 500 mil anúncios em mais de 35.000 cidades e 192 países. Desde sua criação em Novembro de 2008 até Junho de 2012, mais de 10 milhões de reservas foram agendadas via Airbnb.
Random Forest
MSE (Mean Squared Error)
MAE (Mean Absolute Error)
R^2 (R squared)
1. Imports
1.1. Bibliotecas
1.2. Conjunto de dados
1.3. Dicionário das variáveis
2. Análise Exploratória dos dados
2.1. Descrição dos dados
2.2. Respondendo à perguntas
3. Machine Learning
3.1. Preparação dos dados
3.2. Definindo a baseline
3.3. Tranformação dos dados
3.4. Escolhendo o modelo
3.5. Variação no treino dos modelos
3.5.1. 1º Tunning do modelo selecionado
3.5.2. 2º Tunning do modelo selecionado
4. Treinamento com todos os dados
4.1. Previsão nos dados de teste
5. Conclusão
BASELINE:
MSE: 14404.359124812358
MAE: 98.5996424320113
R^2: 0.2230869229876291
MODELO FINAL:
MSE: 0.2199302824060129
MAE: 0.38104048502370497
R^2: 0.41041561263625315
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
from sklearn.model_selection import RandomizedSearchCV
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.svm import SVR
from lightgbm import LGBMRegressor
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import r2_score
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import LabelEncoder
from pprint import pprint
%matplotlib inline
plt.style.use('fivethirtyeight')
pd.set_option('display.max_columns', 100)
import warnings
warnings.filterwarnings("ignore")
# importando o conjunto de dados
df = pd.read_csv("../dados/listings.csv", index_col="id")
# visualizando as primeiras 5 linhas
df.head()
| name | host_id | host_name | neighbourhood_group | neighbourhood | latitude | longitude | room_type | price | minimum_nights | number_of_reviews | last_review | reviews_per_month | calculated_host_listings_count | availability_365 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||
| 17878 | Very Nice 2Br in Copacabana w. balcony, fast WiFi | 68997 | Matthias | NaN | Copacabana | -22.96592 | -43.17896 | Entire home/apt | 221 | 5 | 260 | 2021-02-08 | 2.01 | 1 | 304 |
| 24480 | Nice and cozy near Ipanema Beach | 99249 | Goya | NaN | Ipanema | -22.98570 | -43.20193 | Entire home/apt | 307 | 3 | 85 | 2018-02-14 | 0.67 | 1 | 10 |
| 25026 | Beautiful Modern Decorated Studio in Copa | 3746246 | Ghizlane | NaN | Copacabana | -22.97712 | -43.19045 | Entire home/apt | 160 | 7 | 238 | 2020-02-15 | 1.82 | 11 | 328 |
| 35636 | Cosy flat close to Ipanema beach | 153232 | Patricia | NaN | Ipanema | -22.98816 | -43.19359 | Entire home/apt | 273 | 2 | 181 | 2020-03-15 | 2.02 | 1 | 207 |
| 35764 | COPACABANA SEA BREEZE - RIO - 20 X Superhost | 153691 | Patricia Miranda & Paulo | NaN | Copacabana | -22.98127 | -43.19046 | Entire home/apt | 135 | 3 | 353 | 2021-02-10 | 2.79 | 1 | 101 |
# imprimindo as dimensões
print(f'Quantidade de linhas: {df.shape[0]}')
print(f'Quantidade de colunas: {df.shape[1]}')
Quantidade de linhas: 26615 Quantidade de colunas: 15
Vamos criar agora, um dataframe que nos mostra se temos valores faltando, tipo dos dados e valores unicos em cada coluna.
# criando um dataframe com dados nulos, tipo de dados e valores unicos
pd.DataFrame({'valores_nulos':np.round(df.isnull().mean(), 2),
'tipo_dados': df.dtypes,
'valores_unicos': df.nunique()})
| valores_nulos | tipo_dados | valores_unicos | |
|---|---|---|---|
| name | 0.00 | object | 25807 |
| host_id | 0.00 | int64 | 17324 |
| host_name | 0.00 | object | 5145 |
| neighbourhood_group | 1.00 | float64 | 0 |
| neighbourhood | 0.00 | object | 151 |
| latitude | 0.00 | float64 | 9873 |
| longitude | 0.00 | float64 | 12257 |
| room_type | 0.00 | object | 4 |
| price | 0.00 | int64 | 1694 |
| minimum_nights | 0.00 | int64 | 67 |
| number_of_reviews | 0.00 | int64 | 268 |
| last_review | 0.37 | object | 1469 |
| reviews_per_month | 0.37 | float64 | 492 |
| calculated_host_listings_count | 0.00 | int64 | 49 |
| availability_365 | 0.00 | int64 | 366 |
Podemos ver que a variável neighbourhood_group não possui nenhum valor, enquanto que nas variáveis last_review e reviews_per_month possuem 37% de dados faltando.
Olhando as primeiras linhas e comparando com o tipo dos dados vemos que algumas variáveis estão incorretas, é o caso das variáveis host_id que é do tipo categórica e está como inteiro e a last_review que é do formato data e está como objeto, vamos fazer as devidas transformações em seguida.
E os valores únicos, bom para saber quantitativamente os valores únicos em cada amostra e ter uma idéia sobre seu tipo.
# convertendo variáveis
df['host_id'] = df.host_id.astype('category')
df['last_review'] = pd.to_datetime(df.last_review)
Já sabemos que algumas variáveis possuem dados faltando, vamos dar uma olhada visualmente o quanto representa dentro do nosso conjunto de dados.
# definindo área de plotagem
plt.figure(figsize=(12,5))
# plotando gráfico
sns.heatmap(df.isnull(), cbar=False)
plt.title('Composição do dataframe')
plt.show()
A variável neigbourhood_group pode ser removida, já as variáveis last_review e reviews_per_month vamos ver posteriormente o que podemos fazer.
Vamos dar uma olhada nas distribuições das variáveis numéricas contínuas, plotando suas densidades.
# plotando o gráfico de densidade
# definindo as colunas numéricas
columns_list = ['price', 'minimum_nights', 'number_of_reviews',
'reviews_per_month', 'calculated_host_listings_count', 'availability_365']
# criando objeto para número de linhas e colunas
nrows = 2
ncols = 3
# definindo área de plotagem
fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(20,8))
fig.subplots_adjust(hspace=1, wspace=1)
# criando loop para plotagem
idx = 0
for col in columns_list:
idx += 1
plt.subplot(nrows, ncols, idx)
sns.kdeplot(df[col], shade=True)
plt.title(col, fontsize=10)
plt.tight_layout()
Vamos analisar:
Com intuito de entender um pouco melhor, vamos ver as principais estatísticas com o método describe() do pandas.
# verificando as principais estatísticas numéricas
df[columns_list].describe()
| price | minimum_nights | number_of_reviews | reviews_per_month | calculated_host_listings_count | availability_365 | |
|---|---|---|---|---|---|---|
| count | 26615.000000 | 26615.000000 | 26615.000000 | 16657.000000 | 26615.000000 | 26615.000000 |
| mean | 742.589254 | 4.725268 | 12.146308 | 0.629190 | 9.665414 | 219.438174 |
| std | 5368.868834 | 19.102522 | 29.722813 | 0.876064 | 35.942124 | 141.525405 |
| min | 0.000000 | 1.000000 | 0.000000 | 0.010000 | 1.000000 | 0.000000 |
| 25% | 157.000000 | 1.000000 | 0.000000 | 0.090000 | 1.000000 | 88.000000 |
| 50% | 280.000000 | 2.000000 | 2.000000 | 0.270000 | 1.000000 | 254.000000 |
| 75% | 550.000000 | 4.000000 | 9.000000 | 0.850000 | 3.000000 | 363.000000 |
| max | 625216.000000 | 1000.000000 | 446.000000 | 29.530000 | 295.000000 | 365.000000 |
Agora ver mais claramente a dimensão dos valores em relação suas distribuições, tomando a variável price como exemplo, 75% tem valores abaixo de 550 e seu valor máximo de 652216. Vamos tratar isso mais a frente.
Vamos dar uma olhada nas estatísticas das variáveis categóricas.
# verificando as principais estatísticas categóricas
df.describe(include='O')
| name | host_name | neighbourhood | room_type | |
|---|---|---|---|---|
| count | 26586 | 26591 | 26615 | 26615 |
| unique | 25807 | 5145 | 151 | 4 |
| top | Apartamento em Copacabana | Daniel | Copacabana | Entire home/apt |
| freq | 34 | 312 | 7712 | 19285 |
Vamos dar uma olhada mais uma vez nas distribuições, agora como um boxplot individualmente.
# definindo área de plotagem
fig, ax = plt.subplots(6, 1, figsize=(10,6))
# criando loop para plotagem
idx_ = 0
for i in columns_list:
idx_ += 1
plt.subplot(6, 1, idx_)
df[i].plot(kind='box', vert=False)
plt.tight_layout()
Precisamos tratar essas variáveis, porque os outliers influenciam diretamente no nosso modelo preditivo. Há várias técnicas para remover os outliers, como pelo z-score e pelo quantidade do interquartil por exemplo, mas vamos fazer algo mais simples definindo uma linha de corte pela concentração do volume de dados.
Por exemplo, a variável price concentra-se até em torno de 600, então definiremos os valores até 650. Da mesma forma definiremos para as variáveis minimum_nights e number_of_reviews.
Um ponto que devemos levar em consideração é a quantidade de amostras, quanto mais removemos, menor a quantidade para treinarmos o nosso modelo.
# reduzindo a quantidade de amostras da variável 'price'
df_mod = df[df['price'] < 600]
# reduzindo a quantidade de amostras da variável 'minimum_nights'
df_mod = df_mod[df_mod['minimum_nights'] < 8]
# reduzindo a quantidade de amostras da variável 'number_of_reviews'
df_mod = df_mod[df_mod['number_of_reviews'] < 10]
# reduzindo a quantidade de amostras da variável 'calculated_host_listings_count'
df_mod = df_mod[df_mod['calculated_host_listings_count'] < 70]
# df_mod[columns_list].describe()
# Q1 = df[columns_list].quantile(0.25)
# Q3 = df[columns_list].quantile(0.75)
# IQR = Q3 - Q1
# print(IQR)
# df_num = df[columns_list][~((df[columns_list] < (Q1 - 1.5 * IQR)) | (df[columns_list] > (Q3 + 1.5 * IQR))).any(axis=1)]
Conforme mencionei, com os cortes que fizemos, já reduzimos quase que 50% do conjunto de dados (de 26615 para 14053), optei por manter assim para não reduzirmos mais, mesmo que possa influenciar no nosso modelo.
Vamos novamente plotar os boxplots e observar o quanto melhorou em relação aos outliers.
# definindo área de plotagem
fig, ax = plt.subplots(6, 1, figsize=(10,6))
# criando loop para plotagem
idx_ = 0
for i in columns_list:
idx_ += 1
plt.subplot(6, 1, idx_)
df_mod[i].plot(kind='box', vert=False)
plt.tight_layout()
df_mod.shape
(13210, 15)
Então vamos atualizar nosso conjunto de dados, pelo nosso dataset completo vamos localizar as amostras do dataset modificado pelo índice, utilizando como parâmetro do método loc[].
# criando um novo objeto somente para dados numéricos
df_num = df_mod[columns_list]
# removendo os outliers do dataframe principal
df_new = df.loc[df_num.index.tolist()]
# checando a nova dimensão do dataset
df_new.shape
(13210, 15)
Aquela nossa variável sem dados, vamos removê-la.
# removendo a coluna sem dados
df_new.drop('neighbourhood_group', axis=1, inplace=True)
Com base na latitude e longitude, vamos plotar os pontos em um mapa pela biblioteca plotly com open-street-map, com isso poderemos interagir também, caso desejar.
# plotando o mapa
fig = px.scatter_mapbox(df_new, lat="latitude", lon="longitude", hover_name="name", color_discrete_sequence=["red"],
zoom=10, height=500)
fig.update_layout(mapbox_style="open-street-map")
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
fig.show()
# definindo a área de plotagem
plt.figure(figsize=(10, 8))
# plotando o gráfico
sns.heatmap(df_new.corr(), vmin=-1, vmax=1, annot=True, cmap='vlag')
plt.title('Heatmap com as correlações')
plt.show()