#!/usr/bin/env python
# coding: utf-8
# ![Airbnb_banner.png](attachment:76ff8d77-df97-4a42-a9c9-8aa6d7addab2.png)
#
# # Содержание
#
# - [1. Цели](#objectives)
# - [2. Подготовка и разведочный анализ данных](#dataprep)
# - [3. Обзор собственников и отзывов](#hosts)
# - [4. Обзор расположений](#location)
# - [5. Анализ эмоциональной тональности отзывов клиентов](#sentiment)
# - [6. Заключение](#conclusion)
#
#
# # 1. Цели
#
#
# - Определить собственников с наибольшим количеством отзывов и прогнозируемым доходом. Их профиль и контактная информация могут быть использованы для предложения услуг, таких как уборка и других видов работ по обслуживанию недвижимости.
# - Показать в каких районах и какой тип недвижимости преобладает в списках наиболее популярных собственников
# - Показать среднюю динамику цен за четыре квартала для всех собственников
# - Проанализировать списки удобств в объявлениях. Какие слова самые популярные?
# - Показать лучшие районы:
#
# - по количеству объявлений. Сколько объявлений находится в каждом месте?
# - по среднему прогнозируемому ежемесячному доходу
#
# Визуализировать связь между районами, количеством спален и средней ценой.
#
Какие удобства упоминаются чаще всего в этих объявлениях?
# - Выполнить анализ эмоциональной тональности отзывов на объявления наиболее популярных собственников и показать самые используемые слова.
#
#
#
# # 2. Подготовка и разведочный анализ данных
#
# ## Импорт и настройка пакетов
# In[11]:
import pyodbc
import pandas as pd
import numpy as np
import matplotlib.pylab as plt
import seaborn as sns
import folium
from folium.plugins import MarkerCluster
from folium import plugins
from folium.plugins import FastMarkerCluster
from folium.plugins import HeatMap
from sqlalchemy import create_engine
from collections import Counter
from wordcloud import WordCloud
import re
pd.set_option("display.max_rows",100)
pd.set_option("display.max_columns",100)
plt.style.use('ggplot')
#
# Данные с подробными сведениями об объявлениях и отзывах за четыре квартала, до июня 2023 года. Все файлы взяты с веб-ресурса
Inside Airbnb.
# CSV файлы сначала были загружены в базу данных SQL Server, а затем записи из отдельных файлов были объединены в одну таблицу.
#
# In[13]:
db_engine = create_engine('mssql+pyodbc://sa:8biq9V6NJxpUP4kSQVqi@system/AirBnb?driver=ODBC+Driver+17+for+SQL+Server')
# In[14]:
'''
4 quarter periods and last_scraped values for each one
Sep 9 2022:
2022-09-09
2022-09-10
Dec 6 2022:
2022-12-06
2022-12-07
2022-12-25
Mar 7 2023:
2023-03-07
2023-03-08
2023-03-09
Jun 6 2023:
2023-06-06
2023-06-07
2023-06-08
'''
listing = pd.read_sql_table('listing_la', db_engine)
review = pd.read_sql_table('review_la', db_engine)
listing.head()
#
# Здесь мы рассматриваем таблицу объявлений, отмечая размеры, типы столбцов и пропущенные значения.
#
# Всего, более 173 тысяч объявлений, описанных 75 атрибутами.
#
# In[16]:
# displays the dimenstions of the listings dataset
listing.shape
# In[17]:
# displays types for listings data
listing.dtypes
# In[18]:
# checks missing values in all columns
listing.isna().sum().sort_values(ascending=False)
# In[19]:
# removes select columns
listing_mod = listing.drop(columns=["scrape_id", "source", "neighborhood_overview", "picture_url", "host_location", "host_about", \
"host_thumbnail_url", "host_picture_url", "host_neighbourhood", "host_verifications", "host_has_profile_pic", \
"host_identity_verified", "bathrooms", "minimum_minimum_nights", "maximum_minimum_nights", "minimum_maximum_nights", \
"maximum_maximum_nights", "minimum_nights_avg_ntm", "maximum_nights_avg_ntm", "calendar_updated", "calendar_last_scraped", \
"number_of_reviews_ltm", "number_of_reviews_l30d", "review_scores_accuracy", "review_scores_cleanliness", \
"review_scores_checkin", "review_scores_communication", "review_scores_value", "instant_bookable", "calculated_host_listings_count", \
"calculated_host_listings_count_entire_homes", "calculated_host_listings_count_private_rooms", \
"calculated_host_listings_count_shared_rooms", "reviews_per_month"])
# In[20]:
listing_mod.dtypes
#
# Мы выбрали некоторые атрибуты, которые можно удалить из набора данных, поскольку они являются неактуальными или все значения отсутствуют.
#
# Четыре столбца, указанные ниже, имели тип данных, состоящий из чисел с плавающей запятой, и были преобразованы в целые числа.
#
# In[22]:
# displays and fills NA values for some columns before type conversion
temp = listing_mod[['id','host_listings_count','host_total_listings_count','bedrooms','beds']]
temp[temp.isna().any(axis=1)]
listing_mod.fillna({'host_listings_count':0,'host_total_listings_count':0,'bedrooms':0,'beds':0}, inplace = True)
# converts columns type from float to integer
listing_mod = listing_mod.astype({'host_listings_count':'int64','host_total_listings_count':'int64','bedrooms':'int64','beds':'int64'})
listing_mod.info()
#
# Следующие три столбца имеют только некоторые повторяющиеся значения и более пригодны для категориальных типов данных.
#
# In[24]:
# explores the unique values in these columns
print('Уникальные значения в "Neighbourhood": ')
print(listing_mod.neighbourhood_group_cleansed.unique())
print('Уникальные значения в "Room type": ')
print(listing_mod.room_type.unique())
print('Уникальные значения в "Host response time": ')
print(listing_mod.host_response_time.unique())
# converts types to a category
listing_mod = listing_mod.astype({'neighbourhood_group_cleansed':'category','room_type':'category', \
'host_response_time':'category'})
listing_mod.dtypes
# ### Проверка на дубликаты
#
# Дубликаты в подмножестве выбранных столбцов в таблице объявлений не обнаружены.
#
# In[26]:
# checks duplicate records for all columns
print('Количество дубликатов: ' + str(listing_mod.duplicated().sum()))
# checks duplicates on a specific subset of columns
listing_mod.duplicated(subset = ['id', 'last_scraped', 'host_id']).sum()
# ## Исследование атрибутов
# ### Корреляция
# In[29]:
listing_corr = listing_mod[['bedrooms','beds','price','number_of_reviews', 'minimum_nights']].dropna().corr()
sns.heatmap(listing_corr, annot=True)
plt.show()
#
# Диаграмма выше показывает, как определенные численные атрибуты соотносятся друг с другом.
#
# ### Удаление выбросов в столбце Price
# In[32]:
# identifies and removes outliers in the price column
plt.figure(figsize=(12,6))
plt.title('Распределение цены')
sns.boxplot(data=listing_mod, x='price')
plt.show()
listing_mod.price.describe()
#
# Подход IQR (интерквартильный диапазон) был применён для фильтрации объявлений и удаления выбросов больше значения 2182 USD за сутки для более чистого и реалистичного анализа.
#
# In[34]:
Q1 = listing_mod.price.quantile(0.25)
Q3 = listing_mod.price.quantile(0.75)
IQR = Q3 - Q1
lower_limit = Q1 - IQR * 1.5
upper_limit = Q3 + IQR * 12
print(f'Нижний предел: {lower_limit}\nВерхний предел: {upper_limit}')
# since the lower limit is negative, let's check if there is any prices below zero
print(f'Записи с ценами ниже нуля: {len(listing_mod[listing_mod.price < 0].index)}')
# we pick big factor of 12 to give room for listings with reasonably high prices considering the location and the property size
listing_mod = listing_mod[~(listing_mod.price > upper_limit)]
print(f'Обновлённое количество строк: {len(listing_mod.index)}')
#
# Поскольку мы загрузили четыре файла по одному на каждый квартальный период, даты используются в качестве ссылок для определения каждого периода.
#
# У нас есть несколько дат, когда была собрана информация, и одна дата, когда были сохранены большинство записей.
#
# Например, период, датированный 6 июня 2023 года, также содержит некоторые записи, датированные двумя следующими днями.
#
#
# Июнь 6 2023:
# 2023-06-06
# 2023-06-07
# 2023-06-08
#
# Эти даты заменены последним значением в каждом квартале для более чистой группировки.
#
#
# In[36]:
# shows unique dates of scraped data for all 4 quarters
listing_mod.groupby('last_scraped').size()
# substitutes dates with the latest value in each quarter for cleaner grouping
listing_mod.loc[listing_mod.last_scraped == '2022-09-09', 'last_scraped'] = '2022-09-10'
listing_mod.loc[listing_mod.last_scraped == '2022-12-06', 'last_scraped'] = '2022-12-25'
listing_mod.loc[listing_mod.last_scraped == '2022-12-07', 'last_scraped'] = '2022-12-25'
listing_mod.loc[listing_mod.last_scraped == '2023-03-07', 'last_scraped'] = '2023-03-09'
listing_mod.loc[listing_mod.last_scraped == '2023-03-08', 'last_scraped'] = '2023-03-09'
listing_mod.loc[listing_mod.last_scraped == '2023-06-06', 'last_scraped'] = '2023-06-08'
listing_mod.loc[listing_mod.last_scraped == '2023-06-07', 'last_scraped'] = '2023-06-08'
# displays 4 unique dates: one per quarter
listing_mod.groupby('last_scraped').size()
#
# # 3. Обзор собственников и отзывов
# В этом разделе исследуются 25 собственников с самой высокой прогнозируемой ежемесячной выручкой.
#
# In[38]:
# identifies top 25 hosts by their projected revenue (average for all quarters)
listing_mod['projected_revenue_30d'] = listing_mod['price'] * (30 - listing_mod['availability_30'])
top_hosts_rev = listing_mod.groupby('host_id')['projected_revenue_30d'].mean().sort_values(ascending=False).head(25)
top_hosts_rev = listing_mod.loc[listing_mod['host_id'].isin(top_hosts_rev.index), \
['host_id', 'host_name', 'host_since', 'neighbourhood_cleansed', 'property_type', 'projected_revenue_30d', 'license']]
# shows how many hosts are licensed
host_lic_dist = top_hosts_rev[['host_id','license']]
host_lic_dist = host_lic_dist.fillna({'license': 'None'})
host_lic_dist = host_lic_dist.drop_duplicates()
plt.figure(figsize=(4,2))
host_lic_dist.license.value_counts().plot(kind='bar')
plt.title("Распределение Лицензий Среди Лучших Собственников")
plt.xlabel('Лицензия')
plt.ylabel('Количество')
plt.xticks(rotation=75)
plt.show()
#
# График показывает, что большинство из топ-собственников не имеют официальной лицензии.
#
# In[40]:
# lists information about top 25 hosts by projected revenue(30 days)
top_hosts_rev = top_hosts_rev.groupby(['host_id', 'host_name', 'host_since', 'neighbourhood_cleansed', 'property_type']) \
.agg(average_proj_revenue=('projected_revenue_30d','mean')).sort_values('average_proj_revenue', ascending=False).reset_index()
top_hosts_rev
# In[41]:
# displays the distribution of neighbourhoods for the top hosts
loc_dist = top_hosts_rev.neighbourhood_cleansed.value_counts()
plt.figure(figsize=(5, 4))
sns.countplot(y='neighbourhood_cleansed', data=top_hosts_rev, order=loc_dist.index, orient='h',edgecolor=None)
plt.title("Распределение по Районам (Топ Собственники)")
plt.xlabel("Количество")
plt.ylabel("Районы")
plt.show()
#
# Собственники, которые приносят наибольший доход с помощью своих объявлений, имеют средний доход в диапазоне от 50 850 до 63 000 USD. Наиболее популярными районами, в которых они расположены, являются: Malibu, Beverly Crest, Westwood, Venice and Beverly Grove.
#
#
# In[43]:
# displays the distribution of property types for the top hosts
prop_dist = top_hosts_rev['property_type'].value_counts()
#plt.style.use('ggplot')
plt.pie(prop_dist, labels=prop_dist.index, autopct="%.1f%%")
plt.title("Распределение по Виду Собственности(Топ Собственники)")
plt.show()
#
# Такой тип собственности как целый дом и арендуемая единица доминируют в объявлениях самых популярных собсвтенников с долей более 80%.
#
#
# ### Средняя цена среди всех собственников
# Датасет содержит четыре отрезка времени, покрывающих информацию об объявлениях за период примерно с **июня 2022** до **6 июня 2023**
# In[46]:
price_mean = listing_mod.groupby('last_scraped').agg({'price':'mean'})
plt.figure(figsize=(5, 3))
sns.lineplot(x='last_scraped', y='price', data=price_mean, label='Цена')
plt.title("Средняя Цена за Период Времени")
plt.xlabel("Дата")
plt.ylabel("Цена(USD)")
# rotates x-axis labels for readability
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
#
# Поскольку у нас недостаточно значений дат цены для анализа, мы отмечаем, что цены меняются довольно резко.
#
# Это показывает нам тренд, при котором собственники снижают цены ближе к концу года, чтобы увеличить спрос в этом последнем квартале 2022 года.
#
# ## Топ-25 объявлений по количеству отзывов
# In[49]:
# identifies top 25 listings with the largest number of reviews
# shows all listings across all dates when the data was collected
review_group = review.groupby('listing_id')['listing_id'].count().sort_values(ascending=False).head(25)
listing_25 = listing_mod.loc[listing_mod.id.isin(review_group.index),['id', 'listing_url', 'name', 'host_id', \
'host_name', 'last_scraped', 'neighbourhood_cleansed', 'property_type', \
'number_of_reviews']].sort_values('number_of_reviews', ascending=False)
print(f'\nРаспределение по виду собственности:\n\n{listing_25.property_type.value_counts()}')
listing_25.head(15)
#
# Таблица выше демонстрирует информацию об объявлениях с немного другого ракурса -- наибольшее количество обзоров. Виды собственности объектов распределены здесь более равномерно.
#
# Большинство предложений представляют собой весь гостевой дом, частную комнату в доме и номер в бутик-отеле.
#
#
# # 4. Обзор расположений
#
#
Все объявления на карте
# Визуализация карты показывает географическое распределение и плотность более чем 67 000 уникальных месторасположений по всему Лос-Анджелесу Даунтауну, Sherman Oaks, Hollywood, Venice
#
и другим прибрежным районам, которые и содержат большинство объявлений.
#
#
# In[53]:
unique_coordinates = listing_mod.drop_duplicates(subset=['latitude', 'longitude'])
locations = list(zip(unique_coordinates.latitude, unique_coordinates.longitude))
init_long = -118.36
init_lat = 34.06
map_la_listings = folium.Map(location=[init_lat, init_long], zoom_start=9)
HeatMap(locations,min_opacity=0.25).add_to(map_la_listings)
FastMarkerCluster(data=locations, radius=5).add_to(map_la_listings)
map_la_listings
# ## Лучшие районы по количеству объявлений
# In[55]:
neig_dist = listing_mod.neighbourhood_cleansed.value_counts().head(50)
plt.figure(figsize=(12,10))
plt.title("50 Лучших Районов по Количеству Объявлений", fontsize=16)
plt.xlabel('Количество Объявлений', fontsize=12)
reversed_palette = sns.color_palette("inferno", n_colors=len(neig_dist))[::-1]
sns.countplot(y='neighbourhood_cleansed', data=listing_mod, order=neig_dist.index, palette=reversed_palette)
plt.ylabel('Районы')
plt.xlabel('Количество Объявлений')
# adds listings count beside each neighbourhood
for index, value in enumerate(neig_dist):
plt.text(value + 20, index, str(value), fontsize=8, va='center', weight='bold')
plt.show()
#
#
Лучшие районы по среднему прогнозируемому месячному доходу
# Ниже перечислены 25 лучших районов по среднему прогнозируемому ежемесячному доходу. Ключевые выводы:
#
# - Emerald Bay возглавляет список с доходом 18 650 долларов в среднем.
# - Наибольшая часть лучших районов представляют прибрежные территории, такие как Newport Beach, Palos Verdes, Malibu, Manhattan Beach, Laguna Beach и другие. Близость к пляжам создает сильный спрос.
# - Самые прибыльные районы имеют средние цены в объявлениях от 400 до 800 долларов, указывающие на "золотую середину", сбалансированную между спросом и доходами.
# - Целые дома как вид собственности доминируют в лучших районах. Услуги группам и семьям могут способствовать увеличению доходов.
# - Районами в холмистых северо-западных частях Лос-Анджелеса, таких как Hollywood Hills и Pacific Palisades, также заняли высокие позиции в рейтинге. Сыграла роль живописного вида и близость к достопримечательностям.
#
#
# In[57]:
neighbourhoods = listing_mod.groupby('neighbourhood_cleansed', as_index=False)
top_loc = pd.DataFrame({'Neighbourhood': pd.Series(dtype='str'),
'Mean Projected Revenue(30d)':pd.Series(dtype='float'),
'Mean Price': pd.Series(dtype='float'),
'Property Type': pd.Series(dtype='str')})
# calculates most used property type for a neighbourhood and adds the row to the temporary table
for neighb, data in neighbourhoods:
projected_revenue = data['projected_revenue_30d'].mean()
price = data['price'].mean()
property_type = data['property_type'].mode()[0]
top_loc.loc[len(top_loc)] = [neighb, projected_revenue, price, property_type]
# Mean Projected Revenue(30d) value is relative to all listings in the neighbourhood and not the whole neighbourhood itself
top_loc = top_loc.sort_values('Mean Projected Revenue(30d)', ascending=False).head(25)
top_loc
# In[58]:
loc_rooms_price = listing_mod[listing_mod.neighbourhood_cleansed.isin(top_loc.Neighbourhood)]
loc_rooms_price = loc_rooms_price.groupby(['neighbourhood_cleansed', 'bedrooms']).price.mean().unstack()
loc_rooms_price.sort_values('neighbourhood_cleansed', ascending=False)
plt.figure(figsize=(10,8))
sns.heatmap(loc_rooms_price, annot=True, fmt=".0f")
plt.ylabel('Районы')
plt.xlabel('Количество спален')
plt.show()
#
# Мы уже выявляли, что тип собственности в виде целого дома является основным типом для лучших мест. Этот график позволяет нам увидеть средние цены всех объявлений, разбитые по месторасположениям и количеству спальных комнат.
#
# Самыми дорогими микрорайонами в целом являются Bel-Air, Malibu, Beverly Crest, Pacific Palisades и Unincorporated Santa Monica Mountains.
# Средние цены этих микрорайонов колеблются от 400 до более 1500 долларов за ночь.
#
Мы также замечаем тенденцию среди лучших микрорайонов. Например, Bel-Air показывает значительный скачок цен с 2-х до 3-х спальных комнат, в среднем 879 долларов за трёхспальные комнаты в сравнении с 303 долларами за две спальни.
#
Newport Beach имеет наибольшую доступность среди более крупных объектов 4-8 спальных комнат среди лучших районов.
#
#
#
#
Удобства в объявлениях
# Давайте взглянем на удобства, указанные в объявлениях. Два графика рядом друг с другом дают нам понимание того, что популярно и важно не только в топовых локациях, но и в целом.
#
# Мы видим много пересечений. Это подтверждает, что новые хозяева должны обратить внимание на эти наиболее упомянутые удобства, чтобы соответствовать высочайшим ожиданиям клиентов.
#
#
#
# In[61]:
amenities = listing_mod.amenities.to_list()
amenities_top_loc = listing_mod[listing_mod.neighbourhood_cleansed.isin(top_loc.Neighbourhood)].amenities.to_list()
import matplotlib.gridspec as gridspec
# extracts individual words from amenities column
words = []
for item in amenities:
words_lst = item.strip('][').split(', ')
words.extend(words_lst)
amenities_counter = Counter(words)
# gets the top 25 most common words
top_words = amenities_counter.most_common(25)
top_words_df = pd.DataFrame(top_words)
top_words_df.rename(columns={0:'Words', 1:'Count'}, inplace=True)
# extracts individual words from amenities column
words_loc = []
for item in amenities_top_loc:
words_lst_loc = item.strip('][').split(', ')
words_loc.extend(words_lst_loc)
amenities_counter_loc = Counter(words_loc)
# gets the top 25 most common words for neighbourhoods
top_words_loc = amenities_counter_loc.most_common(25)
top_words_loc_df = pd.DataFrame(top_words_loc)
top_words_loc_df.rename(columns={0:'Words', 1:'Count'}, inplace=True)
# plots
gs = gridspec.GridSpec(1, 2, width_ratios=[1.5,2])
fig = plt.figure(figsize=(25,14),dpi=300)
ax = plt.subplot(gs[0,0])
reversed_palette = sns.color_palette("inferno", n_colors=25)[::-1]
ax = sns.barplot(x='Count', y='Words', data=top_words_df, palette=reversed_palette, alpha=0.9)
ax.set_title('Топ 25 Наиболее Упоминаемых Удобств (Все Объявления)\n', fontsize=24)
ax.set_xlabel('Количество слов', fontsize=16)
ax.set_ylabel('Слова', fontsize=16)
ax.tick_params(axis='x', labelsize=16)
ax.tick_params(axis='y', labelsize=16)
ax1=plt.subplot(gs[0,1])
ax1 = sns.barplot(x='Count', y='Words', data=top_words_loc_df, palette=reversed_palette, alpha=0.9)
ax1.set_title('Топ 25 Наиболее Упоминаемых Удобств (Лучшие Районы)\n', fontsize=24)
ax1.tick_params(axis='x', labelsize=16)
ax1.tick_params(axis='y', labelsize=16)
plt.xlabel('Количество слов', fontsize=16)
plt.ylabel('Слова', fontsize=16)
fig.tight_layout()
plt.show()
#
# # 5. Анализ эмоциональной тональности отзывов клиентов
#
# Была использована заранее натренированная модель трансформера для расчёта положительных, нейтральных и отрицательных оценок.
#
Она была обучена на 124 миллионах постах на платформе X(Twitter), охватывающих почти четыре года.
#
Некоторые ключевые результаты:
#
# - Большинство отзывов о лучших собственниках положительные (более 90%), с показателями положительной оценки, в основном, в диапазоне 0.95-0.99.
#
Это говорит о высоком уровне удовлетворенности гостей. Чем этот указатель ближе к единице, тем более отзыв обозначен моделью как положительный.
# - У нас также очень мало отзывов с отрицательным тоном. При рассмотрении текстов оказывается, что это связано с автоматическим размещением отмены бронирований в разумные сроки заранее.
#
Минимальный срок был 16 дней.
# - Наилучшие положительные оценки тональности принадлежат отзывам с похвалой в адрес прекрасных помещения для проживания,
#
потрясающих видов, парковки в помещении, великолепных домов и исключительных собственников.
#
#
# In[64]:
from transformers import AutoTokenizer, AutoConfig
from transformers import AutoModelForSequenceClassification
from scipy.special import softmax
import warnings
warnings.filterwarnings('ignore')
#pd.set_option('display.max_colwidth', None)
# In[ ]:
MODEL = f"cardiffnlp/twitter-roberta-base-sentiment-latest"
tokenizer = AutoTokenizer.from_pretrained(MODEL)
config = AutoConfig.from_pretrained(MODEL)
model = AutoModelForSequenceClassification.from_pretrained(MODEL)
# In[66]:
# max sequence length
MAX_LEN = 512
# uses the pretrained model to get the scores for each review
def get_sentiment(review):
review = review[:MAX_LEN]
encoded_input = tokenizer(review, return_tensors='pt')
output = model(**encoded_input)
scores = output[0][0].detach().numpy()
scores = softmax(scores)
# gets the highest score and fills a sentiment label
max_score = max(scores)
sentiment = 'Neutral'
if scores[0] == max_score:
sentiment = 'Negative'
elif scores[2] == max_score:
sentiment = 'Positive'
return np.array([scores[0], scores[1], scores[2]]), sentiment
# applies sentiment scores and labels to a copy of the dataframe
def apply_sentiment(df):
df_copy = df.copy()
# creates sentiment columns first
df_copy['score_neg'] = None
df_copy['score_neu'] = None
df_copy['score_pos'] = None
df_copy['sentiment'] = None
for idx, row in df.iterrows():
scores, sentiment = get_sentiment(row.comments)
df_copy.at[idx, 'score_neg'] = scores[0]
df_copy.at[idx, 'score_neu'] = scores[1]
df_copy.at[idx, 'score_pos'] = scores[2]
df_copy.at[idx, 'sentiment'] = sentiment
return df_copy
# obtains the top 25 hosts listings ids for mathcing with reviews records
listings_ids = listing_mod.loc[listing_mod['host_id'].isin(top_hosts_rev.host_id), 'id']
review_top_hosts = review[review.listing_id.isin(listings_ids)]
review_sentiment = apply_sentiment(review_top_hosts)
review_sentiment.head()
# In[67]:
# displays the distribution of property types for the top hosts
sent_dist = review_sentiment['sentiment'].value_counts()
plt.figure(figsize=(5,3))
plt.pie(sent_dist, labels=sent_dist.index, autopct="%.1f%%")
plt.title("Распределение Тональности Отзывов (Лучшие собственники)")
plt.show()
#
#
Облаки слов
# Два облака слов иллюстрируют наиболее часто используемые слова, оставленные в отзывах на объявления лучших собственников и во всех объявлениях в целом.
#
В успешных объявлениях мы видим слова такие как "место", "дом", "потрясающе", "любим", "красиво", "семья", "кухня", "гардеробная", "отличный собственник", "бассейн", "местоположение", "вид", "океан", "просторно", "Малибу" и так далее.
#
Эти неформальные слова и фразы указывают на удовлетворенность клиентов условиями проживания, видами и местоположением, выражая позитивное настроение и эмоции.
#
Некоторые настоящие имена хозяев также есть в облаке, указывающие на популярность определённых управляющих, а также внимание и отношение клиентов.
#
Общая диаграмма слов рассказывает о похожем настроении.
#
Список содержит
"отличное местоположение", "чисто", "дом", "отличное место", "настоятельно рекомендую", "отличный хозяин", "определённо остаюсь", "парковка на улице", "быстрый ответ", "Санта-Моника", "тихий район" и так далее. Термины более общие и сконцентрированы на местоположении, виде имущества, условиях проживания и общем удовлетворении.
#
# In[69]:
# fills missing values
print(review.isna().sum())
review.comments = review.comments.fillna('')
print('\n --- Отзывы после вызова fillna: \n')
print(review.isna().sum())
print('\n')
gs = gridspec.GridSpec(1, 2, width_ratios=[1,1])
fig = plt.figure(figsize=(14,10), dpi=250)
ax_r_hosts = plt.subplot(gs[0,0])
ax_r_all = plt.subplot(gs[0,1])
review_words_hosts = " ".join(rev for rev in review_top_hosts.comments)
wordcloud_hosts = WordCloud(width=700, height=400, max_words=350, min_word_length=3, ranks_only=True).generate(review_words_hosts)
ax_r_hosts.set_axis_off()
ax_r_hosts.set_title('Облако Слов (Отзывы лучших собственников)')
ax_r_hosts.imshow(wordcloud_hosts, interpolation='bilinear')
# generates a wordcloud plot
review_words = " ".join(rev for rev in review.comments)
wordcloud = WordCloud(width=700, height=400, max_words=350, min_word_length=3, ranks_only=True).generate(review_words)
ax_r_all.set_axis_off()
ax_r_all.set_title('Облако Слов (Все отзывы)')
ax_r_all.imshow(wordcloud, interpolation='bilinear')
fig.tight_layout()
plt.show()
#
# # 6. Заключение
#
#
# Цели, поставленные для этого проекта, были достигнуты, и этот раздел содержит ключевые выводы, открытия и фактические числа. Объявления AirBnb и отзывы для региона Лос-Анджелеса, Калифорния, были получены из публичного веб-ресурса [Inside AirBnb](http://insideairbnb.com/), который регулярно публикует квартальные данные в виде снимков объявлений, доступных в определенной точке времени.
#
#
Начальная загрузка данных, моделирование и подготовка
# Четыре файла с подробной информацией об объявлениях были загружены и объединены в одну таблицу в базе данных Microsoft Sql Server. 173 168 записей были загружены с 75 столбцами различных типов данных. После просмотра некоторые лишние столбцы были удалены, и остался 41 столбец.
# График цены показал некоторые выбросы со значениями до 100 000 за ночь, которые не подходили для разумного анализа. После установки предела в 2182 долларов, у нас осталась 171 061 запись.
#
#
Собственники и объявления
# Ключевые собственники, генерировавшие доход, заработали от 50 850 до 63 000 в месячном доходе исходя из прогноза, в различных видных районах, таких как Malibu, Beverly Crest, и Westwood.
# Мы рассмотрели динамику цен. Она показывает нам тренд, при котором собственники скидывают цены ближе к концу года, чтобы увеличить спрос в последний квартал 2022 года.
#
#
Районы
# Географическое распределение более 67 000 уникальных мест по всему Лос-Анджелесу было визуализировано, демонстрирующее районы с более высокой плотностью объявлений, такие как Даунтаун, Sherman Oaks, и Hollywood.
#
Оценивая лучшие районы на основе средней прогнозируемой месячной выручки, можно отметить, что прибрежные районы, такие как, Emerald Bay, Newport Beach, и Palos Verdes, занимают высокие позиции по генерации выручки.
#
Анализ показал, что эти прибыльные районы поддерживают баланс между спросом и доходами, предлагая цены, часто находящиеся в диапазоне от 400 до 800 долларов. Доминирование целых домов в этих районах подчеркивает фокус на групповом и семейном проживании.
#
# Списки по количеству спален показали, что цены существенно увеличиваются от 2 до 3+ спален в самых дорогих рафонах.
# Мы также проанализировали наиболее часто упоминаемые удобства в объявлениях. Сопоставив это с лучшими районами, было установлено, что определенные удобства пользуются постоянной популярностью.
# Это подчёркивает важность включения этих удобств для соответствия ожиданиям гостей и повышения удовлетворённости.
#
#
Анализ эмоциональной тональности в отзывах
# Используя предтренированную модель трансформера, был проведён анализ тональности обзоров клиентов. Большинство отзывов демонстрировали высокоположительную тональность, с оценками, значения которых обычно находились в диапазоне 0.95-0.99. Отзывы с негативной тональностью были редкими и касались только автоматической отмены резервирования. Анализ показал положительную тональность, обусловленную такими аспектами, как отличное расположение, потрясающий вид, необходимые удобства и исключительные хозяева.
#
#
#
В заключение, анализ раскрыл данные о наиболее высокооплачиваемых собственниках, оптимальных видов недвижимости, ценах, районах, удобствах и отношении гостей.
#