O Airbnb já é considerado como sendo a maior empresa hoteleira da atualidade. O detalhe é que ele não possui nenhum hotel!
Conectando pessoas que querem viajar (e se hospedar) com anfitriões que querem alugar seus imóveis de maneira prática, o Airbnb fornece uma plataforma inovadora para tornar essa hospedagem alternativa.
No final de 2018, a Startup fundada 10 anos atrás, já havia hospedado mais de 300 milhões de pessoas ao redor de todo o mundo, desafiando as redes hoteleiras tradicionais.
Uma das iniciativas do Airbnb é disponibilizar dados do site, para algumas das principais cidades do mundo. Por meio do portal Inside Airbnb, é possível baixar uma grande quantidade de dados para desenvolver projetos e soluções de Data Science.

Neste notebook, iremos analisar os dados referentes ao Distrito de Lisboa, e ver quais insights podem ser extraídos a partir de dados brutos.
Lisboa é a capital de Portugal e é uma das cidades mais carismáticas e vibrantes da Europa. É uma cidade que mistura o patrimônio tradicional sem esforço, com um impressionante modernismo. Como um destino de férias, Lisboa oferece uma história rica e variada, uma agitada vida noturna e é abençoada com um clima glorioso durante o ano todo.
Lisboa é uma cidade movimentada e animada, possuindo uma grande variedade de atividades e atrações turísticas fascinantes. A cidade tem uma atmosfera acolhedora e liberal, enquanto ainda abraça a sua herança profundamente enraizada e história vasta. Lisboa irá atrair uma seleção diversificada de idades e turistas; pode formar uma viagem cultural, uma extravagância da vida noturna, férias em família, uma pausa relaxante na cidade ou até mesmo como base para umas férias na praia.
A capital portuguesa é reconhecida constantemente como uma das melhores cidades do mundo, uma afirmação confirmada por “Lonely Planet Guides”, que nomeou Lisboa como uma das 10 melhores cidades do mundo. Surpreendentemente, Lisboa ainda é uma das capitais menos visitadas da Europa, mas isso está mudando rapidamente à medida que novos visitantes descobrem o fascínio de Portugal.
Fonte: https://lisbonlisboaportugal.com/lisboa-portugal-guia-br.html
Os dados usados foram obtidos do site Inside Airbnb, com a data de compilação em 28 de maio de 2020.
# Iremos importar os pacotes necessários
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
# importando o arquivo listings.csv para um DataFrame
df = pd.read_csv("http://data.insideairbnb.com/portugal/lisbon/lisbon/2020-04-29/visualisations/listings.csv")
Dicionário das variáveis
id - número de id gerado para identificar o imóvelname - nome da propriedade anunciadahost_id - número de id do proprietário (anfitrião) da propriedadehost_name - Nome do anfitriãoneighbourhood_group - municípioneighbourhood - bairrolatitude - coordenada da latitude da propriedadelongitude - coordenada da longitude da propriedaderoom_type - informa o tipo de quarto que é oferecidoprice - preço para alugar o imóvelminimum_nights - quantidade mínima de noites para reservarnumber_of_reviews - número de reviews que a propriedade possuilast_review - data do último reviewreviews_per_month - quantidade de reviews por mêscalculated_host_listings_count - quantidade de imóveis do mesmo anfitriãoavailability_365 - número de dias de disponibilidade dentro de 365 diasAntes de iniciar qualquer análise, vamos verificar a cara do nosso dataset, analisando as 5 primeiras entradas.
# mostrar as 5 primeiras entradas
df.head()
| id | 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 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 6499 | Belém 1 Bedroom Historical Apartment | 14455 | Bruno | Lisboa | Belm | 38.69750 | -9.19768 | Entire home/apt | 40 | 3 | 26 | 2020-01-03 | 0.38 | 1 | 307 | 
| 1 | 25659 | Sunny, Alfama Sleeps 3 - Coeur d'Alfama | 107347 | Ellie | Lisboa | Santa Maria Maior | 38.71167 | -9.12696 | Entire home/apt | 60 | 3 | 113 | 2019-12-08 | 1.54 | 1 | 317 | 
| 2 | 29248 | Apartamento Alfama com vista para o rio! | 125768 | Bárbara | Lisboa | Santa Maria Maior | 38.71272 | -9.12628 | Entire home/apt | 60 | 3 | 321 | 2020-03-11 | 2.82 | 1 | 351 | 
| 3 | 29396 | Alfama Hill - Boutique apartment | 126415 | Mónica | Lisboa | Santa Maria Maior | 38.71239 | -9.12887 | Entire home/apt | 60 | 1 | 239 | 2020-03-15 | 2.47 | 2 | 318 | 
| 4 | 29720 | TheHOUSE - Your luxury home | 128075 | Francisco | Lisboa | Estrela | 38.71195 | -9.15877 | Entire home/apt | 1100 | 2 | 55 | 2020-03-15 | 0.48 | 2 | 300 | 
Vamos prosseguir e identificar a quantidade de entradas que nosso conjunto de dados possui e ver os tipos de cada coluna.
# identificar o volume de dados do DataFrame
print("Entradas:\t {}".format(df.shape[0]))
print("Variáveis:\t {}\n".format(df.shape[1]))
# verificar as 5 primeiras entradas do dataset
display(df.dtypes)
Entradas:	 24640
Variáveis:	 16
id                                  int64
name                               object
host_id                             int64
host_name                          object
neighbourhood_group                object
neighbourhood                      object
latitude                          float64
longitude                         float64
room_type                          object
price                               int64
minimum_nights                      int64
number_of_reviews                   int64
last_review                        object
reviews_per_month                 float64
calculated_host_listings_count      int64
availability_365                    int64
dtype: object
Este dataset que baixamos é a versão “resumida” do Airbnb, que contém 24640 entradas e 16 variáveis.
A qualidade de um dataset está diretamente relacionada à quantidade de valores ausentes. É importante entender logo no início se esses valores nulos são significativos comparados ao total de entradas.
# Verificar valores ausentes no dataset e colocar em ordem decrescente
(df.isnull().sum() / df.shape[0]).sort_values(ascending=False)
reviews_per_month                 0.168547
last_review                       0.168547
name                              0.000771
host_name                         0.000041
availability_365                  0.000000
calculated_host_listings_count    0.000000
number_of_reviews                 0.000000
minimum_nights                    0.000000
price                             0.000000
room_type                         0.000000
longitude                         0.000000
latitude                          0.000000
neighbourhood                     0.000000
neighbourhood_group               0.000000
host_id                           0.000000
id                                0.000000
dtype: float64
reviews_per_month possui 16,8% dos seus valores faltantes.last_review possui 16,8% dos seus valores faltantes.Para identificar a distribuição das variáveis, irei plotar o histograma.
# plotar o histograma das variáveis numéricas
df.hist(bins=15, figsize=(15,10));

Pela distribuição do histograma, é possível verificar indícios da presença de outliers. Olhe por exemplo as variáveis price, minimum_nights e calculated_host_listings_count.
Os valores não seguem uma distruição, e distorcem toda a representação gráfica. Para confirmar, há duas maneiras rápidas que auxiliam a detecção de outliers. São elas:
describe()boxplots para a variável.# ver o resumo estatístico das variáveis numéricas
df[['price', 'minimum_nights', 'number_of_reviews', 'reviews_per_month',
    'calculated_host_listings_count', 'availability_365']].describe()
| price | minimum_nights | number_of_reviews | reviews_per_month | calculated_host_listings_count | availability_365 | |
|---|---|---|---|---|---|---|
| count | 24640.000000 | 24640.000000 | 24640.000000 | 20487.000000 | 24640.000000 | 24640.000000 | 
| mean | 110.739732 | 3.294440 | 41.781291 | 1.459542 | 15.057224 | 225.239448 | 
| std | 350.763230 | 15.970015 | 65.881125 | 1.461515 | 43.355842 | 132.790977 | 
| min | 0.000000 | 1.000000 | 0.000000 | 0.010000 | 1.000000 | 0.000000 | 
| 25% | 47.000000 | 1.000000 | 2.000000 | 0.320000 | 1.000000 | 118.000000 | 
| 50% | 69.000000 | 2.000000 | 14.000000 | 0.960000 | 3.000000 | 269.000000 | 
| 75% | 100.000000 | 3.000000 | 53.000000 | 2.210000 | 10.000000 | 349.000000 | 
| max | 10000.000000 | 1000.000000 | 877.000000 | 19.890000 | 340.000000 | 365.000000 | 
price possui 75% do valor abaixo de 100, porém seu valor máximo é 10000.minimum_nights) está acima de 365 dias no ano.# minimum_nights
df.minimum_nights.plot(kind='box', vert=False, figsize=(15, 3))
plt.show()
# ver quantidade de valores acima de 30 dias para minimum_nights
print("minimum_nights: valores acima de 30:")
print("{} entradas".format(len(df[df.minimum_nights > 30])))
print("{:.4f}%".format((len(df[df.minimum_nights > 30]) / df.shape[0])*100))

minimum_nights: valores acima de 30:
100 entradas
0.4058%
# price
df.price.plot(kind='box', vert=False, figsize=(15, 3),)
plt.show()
# ver quantidade de valores acima de 1500 para price
print("\nprice: valores acima de 1500")
print("{} entradas".format(len(df[df.price > 1500])))
print("{:.4f}%".format((len(df[df.price > 1500]) / df.shape[0])*100))
# df.price.plot(kind='box', vert=False, xlim=(0,1300), figsize=(15,3));

price: valores acima de 1500
57 entradas
0.2313%
Já que identificamos outliers nas variáveis price e minimum_nights, vamos agora limpar o DataFrame delas e plotar novamente o histograma.
# remover os *outliers* em um novo DataFrame
df_clean = df.copy()
df_clean.drop(df_clean[df_clean.price > 1500].index, axis=0, inplace=True)
df_clean.drop(df_clean[df_clean.minimum_nights > 30].index, axis=0, inplace=True)
df_clean.drop(df_clean[df_clean.calculated_host_listings_count > 60].index, axis=0, inplace=True)
# plotar o histograma para as variáveis numéricas
df_clean.hist(bins=15, figsize=(15,10));

print('Quantidade de variáveis {}'.format(df_clean.shape[0]))
print('Quantidade de entradas {}'.format(df_clean.shape[1]))
df_limpo = (df_clean.shape[0] / df.shape[0]) -1
print('Foram removidos {:.2%} dos dados na limpeza do dataset.'.format(df_limpo * (-1)))
Quantidade de variáveis 23220
Quantidade de entradas 16
Foram removidos 5.76% dos dados na limpeza do dataset.
df_clean[['price', 'minimum_nights','number_of_reviews', 'reviews_per_month', 'calculated_host_listings_count', 
          'availability_365']].describe()
| price | minimum_nights | number_of_reviews | reviews_per_month | calculated_host_listings_count | availability_365 | |
|---|---|---|---|---|---|---|
| count | 23220.000000 | 23220.000000 | 23220.000000 | 19336.000000 | 23220.000000 | 23220.000000 | 
| mean | 97.228424 | 2.716968 | 43.391602 | 1.502402 | 7.262317 | 224.608570 | 
| std | 124.306829 | 3.384602 | 67.243900 | 1.483627 | 10.123960 | 132.692619 | 
| min | 0.000000 | 1.000000 | 0.000000 | 0.010000 | 1.000000 | 0.000000 | 
| 25% | 45.000000 | 1.000000 | 2.000000 | 0.330000 | 1.000000 | 117.000000 | 
| 50% | 69.000000 | 2.000000 | 14.000000 | 1.010000 | 3.000000 | 269.000000 | 
| 75% | 100.000000 | 3.000000 | 56.000000 | 2.300000 | 8.000000 | 349.000000 | 
| max | 1500.000000 | 30.000000 | 877.000000 | 19.890000 | 60.000000 | 365.000000 | 
Correlação significa que existe uma relação entre duas coisas. No nosso contexto, estamos buscando relação ou semelhança entre duas variáveis.
Essa relação pode ser medida, e é função do coeficiente de correlação estabelecer qual a intensidade dela. Para identificar as correlações existentes entre as variáveis de interesse, vou:
seaborn# criar uma matriz de correlação
corr = df_clean[['price', 'minimum_nights', 'number_of_reviews', 'reviews_per_month',
    'calculated_host_listings_count', 'availability_365']].corr()
display(corr)
| price | minimum_nights | number_of_reviews | reviews_per_month | calculated_host_listings_count | availability_365 | |
|---|---|---|---|---|---|---|
| price | 1.000000 | -0.035038 | -0.126661 | -0.124047 | -0.002353 | 0.010003 | 
| minimum_nights | -0.035038 | 1.000000 | -0.051181 | -0.101343 | -0.049146 | -0.023528 | 
| number_of_reviews | -0.126661 | -0.051181 | 1.000000 | 0.780244 | -0.072072 | 0.085859 | 
| reviews_per_month | -0.124047 | -0.101343 | 0.780244 | 1.000000 | -0.063167 | 0.076246 | 
| calculated_host_listings_count | -0.002353 | -0.049146 | -0.072072 | -0.063167 | 1.000000 | 0.040110 | 
| availability_365 | 0.010003 | -0.023528 | 0.085859 | 0.076246 | 0.040110 | 1.000000 | 
sns.heatmap(corr, cmap='RdBu', fmt='.2f', square=True, linecolor='white', annot=True);

A coluna da variável room_type indica o tipo de locação que está anunciada no Airbnb. Se você já alugou no site, sabe que existem opções de apartamentos/casas inteiras, apenas o aluguel de um quarto ou mesmo dividir o quarto com outras pessoas.
Vamos contar a quantidade de ocorrências de cada tipo de aluguel, usando o método value_counts().
# mostrar a quantidade de cada tipo de imóvel disponível
df_clean.room_type.value_counts()
Entire home/apt    16618
Private room        5588
Hotel room           571
Shared room          443
Name: room_type, dtype: int64
#gráfico representando por tipo de imóvel
plt.figure(figsize=(15,5))
sns.countplot(df_clean['room_type'])
plt.title('Tipos de imóveis para se alugar');

# mostrar a porcentagem de cada tipo de imóvel disponível
a = (df_clean.room_type.value_counts() / df_clean.shape[0])
df_clean_a = pd.DataFrame(a)
df_clean_a.style.format('{:.2%}')
| room_type | |
|---|---|
| Entire home/apt | 71.57% | 
| Private room | 24.07% | 
| Hotel room | 2.46% | 
| Shared room | 1.91% | 
Podemos concluir que:
Uma maneira de se verificar uma variável em função da outra é usando groupby(). No caso, queremos comparar os bairros (neighbourhoods) a partir do preço de locação.
df_clean.groupby(['neighbourhood']).price.mean().sort_values(ascending=False)[:10]
neighbourhood
Vermelha                  292.500000
Cardosas                  280.000000
Aveiras de Baixo          269.500000
Ota                       249.000000
Freiria                   220.000000
Peral                     174.800000
Bucelas                   170.300000
So Domingos de Benfica    167.225352
Alcabideche               154.391509
Vale do Paraso            150.000000
Name: price, dtype: float64
Só para dar um único exemplo de como uma amostra pode ser não-representativa, veja quantas entradas há para Vermelha, Cardosas e Aveiras de Baixo.
#ver quantidade de imóveis nos 3 primeiros bairros e a porcentagem em relação ao total de imóveis
print("{} entradas".format(len(df_clean[df_clean.neighbourhood == "Vermelha"].index)))
print("{:.4f}%".format((len(df_clean[df_clean.neighbourhood == "Vermelha"].index) / df.shape[0])*100))
print("{} entradas".format(len(df_clean[df_clean.neighbourhood == "Cardosas"].index)))
print("{:.4f}%".format((len(df_clean[df_clean.neighbourhood == "Cardosas"].index) / df.shape[0])*100))
print("{} entradas".format(len(df_clean[df_clean.neighbourhood == "Aveiras de Baixo"].index)))
print("{:.4f}%".format((len(df_clean[df_clean.neighbourhood == "Aveiras de Baixo"].index) / df.shape[0])*100))
2 entradas
0.0081%
1 entradas
0.0041%
2 entradas
0.0081%
#quantidade de imóveis por bairro
df_clean.neighbourhood.value_counts()
Santa Maria Maior      3450
Misericrdia            2730
Arroios                2235
Cascais e Estoril      1484
So Vicente             1322
                       ... 
Vila Nova da Rainha       1
Cardosas                  1
Alcoentre                 1
Vale do Paraso            1
Ota                       1
Name: neighbourhood, Length: 127, dtype: int64
As variáveis Latitude e Longitude dos imóveis possibilita plotar cada ponto e mapear os imóveis. Considera-se x=longitude e y=latitude.
# plotar os imóveis pela latitude-longitude
df_clean.plot(kind="scatter", x='longitude', y='latitude', alpha=1.0, c=df_clean['price'], s=8,
              cmap=plt.get_cmap('jet'), figsize=(12,8));

Foi feita apenas uma análise superficial na base de dados do Airbnb, porém já se percebeu que existem outliers nas colunas price, minimum_nights e calculated_host_listings_count. Também se notou que há em algumas localidades poucos imóveis disponíveis, o que pode distorcer as informações estatísticas de alguns atributos.
Por fim, lembra-se que este dataset é uma versão resumida, ideal apenas para uma abordagem inicial. Recomenda-se que seja usado, em uma próxima análise exploratória, o conjunto de dados completos, com 106 atributos disponíveis.