Изучение и машинное обучение для листингов на Airbnb в Торонто

Объявления Airbnb, разработка функций, исследовательский анализ, регрессия

Airbnb не предоставляет открытые данные в виде гигантских баз данных или дампов, с которыми мы можем работать. Однако Inside Airbnb использует общедоступную информацию, собранную с веб-сайта Airbnb, и анализирует общедоступную информацию о списках городов на Airbnb, а также предоставляет фильтры и ключевые показатели, чтобы мы могли видеть, как Airbnb используется в крупнейшие города мира. Inside Airbnb - это независимый некоммерческий набор инструментов и данных, который не связан и не одобрен Airbnb или какими-либо конкурентами Airbnb.

Однако информации, предоставленной Inside Airbnb, нам будет недостаточно. Мы собираемся скачать оттуда данные для собственного анализа.

Я буду работать с данными Торонто. Потому что я живу здесь и знаю некоторые районы здесь. Вы можете выбрать любой город, который вам нравится.

Мы собираемся взглянуть на списки и календари Airbnb и попытаться провести некоторый исследовательский анализ в отношении прогнозирования цен на листинги как для, если бы мы гипотетически работали с Airbnb, так и для потребителя. Давайте начнем!

Календарь

calendar = pd.read_csv('calendar.csv.gz')
print('We have', calendar.date.nunique(), 'days and', calendar.listing_id.nunique(), 'unique listings in the calendar data.')

calendar.date.min(), calendar.date.max()

Календарь охватывает период времени в один год, то есть цену и доступность каждый день в течение следующего года. В нашем случае с 2018–10–16 по 2019–10–15.

Наличие в календаре

Когда мы смотрим на данные календаря, мы можем задать такие вопросы, как: насколько загружен он будет для хозяев Airbnb в Торонто в следующем году?

calendar.available.value_counts()

f (false) означает, что недоступен, t (true) означает, что доступен. Чтобы узнать среднесуточную доступность в течение одного года, мы преобразуем доступный столбец в 0, если он доступен, и в 1, если нет.

calendar_new = calendar[['date', 'available']]
calendar_new['busy'] = calendar_new.available.map( lambda x: 0 if x == 't' else 1)
calendar_new = calendar_new.groupby('date')['busy'].mean().reset_index()
calendar_new['date'] = pd.to_datetime(calendar_new['date'])
plt.figure(figsize=(10, 5))
plt.plot(calendar_new['date'], calendar_new['busy'])
plt.title('Airbnb Toronto Calendar')
plt.ylabel('% busy')
plt.show();

Самым загруженным месяцем в Торонто был октябрь, который только что прошел. Следующие напряженные месяцы, похоже, будут после апреля и продолжатся до лета. Все это соответствует нашему опыту и ожиданиям.

Цена в календаре

Как цена меняется от года к месяцу?

Мы удаляем символ «$» в столбце цены и преобразуем его в числовой, а дату преобразуем в тип данных datetime.

calendar['date'] = pd.to_datetime(calendar['date'])
calendar['price'] = calendar['price'].str.replace(',', '')
calendar['price'] = calendar['price'].str.replace('$', '')
calendar['price'] = calendar['price'].astype(float)
calendar['date'] = pd.to_datetime(calendar['date'])
mean_of_month = calendar.groupby(calendar['date'].dt.strftime('%B'),
                                 sort=False)['price'].mean()
mean_of_month.plot(kind = 'barh' , figsize = (12,7))
plt.xlabel('average monthly price');

Цена Airbnb в Торонто увеличивается в июле, августе и октябре. Согласитесь, эти три месяца - лучшие месяцы для посещения Торонто.

Как меняется цена в течение дня недели?

calendar['dayofweek'] = calendar.date.dt.weekday_name
cats = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
price_week=calendar[['dayofweek','price']]
price_week = calendar.groupby(['dayofweek']).mean().reindex(cats)
price_week.drop('listing_id', axis=1, inplace=True)
price_week.plot()
ticks = list(range(0, 7, 1)) # points on the x axis where you want the label to appear
labels = "Mon Tues Weds Thurs Fri Sat Sun".split()
plt.xticks(ticks, labels);

Пятница и суббота более чем на 10 долларов дороже, чем в остальные дни недели.

Списки

Количество объектов в каждом районе

listings = pd.read_csv('listings.csv.gz')
print('We have', listings.id.nunique(), 'listings in the listing data.')

listings.groupby(by='neighbourhood_cleansed').count()[['id']].sort_values(by='id', ascending=False).head(10)

Район с наибольшим количеством объявлений - это Waterfront Communities-The Island, что почти в четыре раза больше, чем второй по величине район (Ниагара). Из приведенной выше карты заголовка мы тоже это видим.

Оценка по отзывам

plt.figure(figsize=(12,6))
sns.distplot(listings.review_scores_rating.dropna(), rug=True)
sns.despine()
plt.show();

listings.review_scores_rating.describe()

Как и ожидалось, большинство рецензентов оставляют высокие баллы.

Изучение цены

Столбец цен требует некоторой очистки, например удаления символа «$» и преобразования его в числовой формат.

listings['price'] = listings['price'].str.replace(',', '')
listings['price'] = listings['price'].str.replace('$', '')
listings['price'] = listings['price'].astype(float)
listings['price'].describe()

Самый дорогой листинг на Airbnb в Торонто - 12933 доллара за ночь. Судя по URL-адресу, насколько я могу судить, это кажется законным. Пентхаус для коллекционеров произведений искусства в самом стильном районе Торонто. Отлично!

Чтобы не попасть под влияние крайних случаев, я решил удалить объявления, стоимость которых превышает 600 долларов за ночь, а также 7 объявлений по цене 0 для следующего исследовательского анализа.

Распределение цен на объявления после удаления выбросов

listings.loc[(listings.price <= 600) & (listings.price > 0)].price.hist(bins=200)
plt.ylabel('Count')
plt.xlabel('Listing price in $')
plt.title('Histogram of listing prices');

Район против цены

Не только прибрежные сообщества - у острова самое большое количество предложений, он также имеет самую высокую среднюю цену, а Милликен имеет самую низкую медианную цену.

тип собственности и цена

Когда мы смотрим на среднюю цену для каждого типа недвижимости, мы должны быть осторожны, чтобы не сказать: «Самый дорогой тип недвижимости - это апарт-отель, а средняя цена у палатки и парковочного места выше, чем у апартаментов и замка», потому что в апарт-отеле, уходе и парковке есть только одно объявление.

тип номера и цена

Само собой разумеется, что средняя цена за весь номер / квартиру намного выше, чем за другие типы номеров.

listings.loc[(listings.price <= 600) & (listings.price > 0)].pivot(columns = 'room_type', values = 'price').plot.hist(stacked = True, bins=100)
plt.xlabel('Listing price in $');

Дом / квартира целиком также имеет наибольшее количество объявлений. Inside Airbnb указал, что целые дома или квартиры, доступные круглый год для туристов, вероятно, без присутствия владельца, могут быть незаконными и, что более важно, вытесняют жителей. Мы отложим на время наши заботы.

тип кровати и цена

Здесь нет ничего удивительного.

Удобства

Текстовое поле «Удобства» требует небольшой очистки.

listings.amenities = listings.amenities.str.replace("[{}]", "").str.replace('"', "")
listings['amenities'].head()

20 самых распространенных удобств.

pd.Series(np.concatenate(listings['amenities'].map(lambda amns: amns.split(","))))\
    .value_counts().head(20)\
    .plot(kind='bar')
ax = plt.gca()
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right', fontsize=12)
plt.show();

Wi-Fi, отопление, предметы первой необходимости, кухня, детектор дыма и т. Д. Являются одними из самых распространенных удобств.

Удобства и 20 самых высоких цен

amenities = np.unique(np.concatenate(listings['amenities'].map(lambda amns: amns.split(","))))
amenity_prices = [(amn, listings[listings['amenities'].map(lambda amns: amn in amns)]['price'].mean()) for amn in amenities if amn != ""]
amenity_srs = pd.Series(data=[a[1] for a in amenity_prices], index=[a[0] for a in amenity_prices])
amenity_srs.sort_values(ascending=False)[:20].plot(kind='bar')
ax = plt.gca()
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right', fontsize=12)
plt.show();

Интересно, что функция удобств, похоже, имеет некоторую связь с ценой.

Количество кроватей и цена

listings.loc[(listings.price <= 600) & (listings.price > 0)].pivot(columns = 'beds',values = 'price').plot.hist(stacked = True,bins=100)
plt.xlabel('Listing price in $');

Подавляющее большинство объявлений имеют одну кровать, а цены на одну кровать очень разные. Есть объявления, в которых нет кровати.

sns.boxplot(y='price', x='beds', data = listings.loc[(listings.price <= 600) & (listings.price > 0)])
plt.show();

Интересно отметить, что средняя цена для объявлений без кроватей выше, чем для объявлений с 1 и 2 кроватями, а средняя цена для объявлений с 10 кроватями очень низка.

Числовые функции

Мы выбираем несколько числовых характеристик и пытаемся исследовать их все вместе.

col = ['host_listings_count', 'accommodates', 'bathrooms', 'bedrooms', 'beds', 'price', 'number_of_reviews', 'review_scores_rating', 'reviews_per_month']
sns.set(style="ticks", color_codes=True)
sns.pairplot(listings.loc[(listings.price <= 600) & (listings.price > 0)][col].dropna())
plt.show();

corr = listings.loc[(listings.price <= 600) & (listings.price > 0)][col].dropna().corr()
plt.figure(figsize = (6,6))
sns.set(font_scale=1)
sns.heatmap(corr, cbar = True, annot=True, square = True, fmt = '.2f', xticklabels=col, yticklabels=col)
plt.show();

Есть и неплохие новости, например, количество спален и комнат, похоже, коррелирует с ценой. Так же совмещены спальные места, кровати и спальни, одну из них оставим для модели.

Моделирование листинговых цен

Предварительная обработка данных и разработка функций

Очистите функцию цены. Особенность, которую мы собираемся смоделировать и спрогнозировать.

listings['price'] = listings['price'].str.replace(',', '')
listings['price'] = listings['price'].str.replace('$', '')
listings['price'] = listings['price'].astype(float)
listings = listings.loc[(listings.price <= 600) & (listings.price > 0)]

Матрица терминологического документа для функции удобства.

Замените значения в следующей функции на 0, если «f», на 1, если «t».

columns =  ['host_is_superhost', 'host_identity_verified', 'host_has_profile_pic',
                   'is_location_exact', 'requires_license', 'instant_bookable',
                   'require_guest_profile_picture', 'require_guest_phone_verification']
for c in columns:
    listings[c] = listings[c].replace('f',0,regex=True)
    listings[c] = listings[c].replace('t',1,regex=True)

Таким же образом очистите и другие столбцы денежных значений.

listings['security_deposit'] = listings['security_deposit'].fillna(value=0)
listings['security_deposit'] = listings['security_deposit'].replace( '[\$,)]','', regex=True ).astype(float)
listings['cleaning_fee'] = listings['cleaning_fee'].fillna(value=0)
listings['cleaning_fee'] = listings['cleaning_fee'].replace( '[\$,)]','', regex=True ).astype(float)

Ниже перечислены числовые функции, которые мы будем использовать.

Заполните недостающие значения в числовых объектах с помощью медианы.

for col in listings_new.columns[listings_new.isnull().any()]:
    listings_new[col] = listings_new[col].fillna(listings_new[col].median())

Обработка и добавление категориальных функций.

for cat_feature in ['zipcode', 'property_type', 'room_type', 'cancellation_policy', 'neighbourhood_cleansed', 'bed_type']:
    listings_new = pd.concat([listings_new, pd.get_dummies(listings[cat_feature])], axis=1)

Добавьте матрицы документов Term, которые мы создали ранее с помощью функции «Удобства».

listings_new = pd.concat([listings_new, df_amenities], axis=1, join='inner')

Предварительная обработка данных и разработка функций завершены!

Регрессор случайного леса

Важность случайного леса

coefs_df = pd.DataFrame()
coefs_df['est_int'] = X_train.columns
coefs_df['coefs'] = rf.feature_importances_
coefs_df.sort_values('coefs', ascending=False).head(20)

LightGBM

Важность функций LightGBM

feat_imp = pd.Series(clf.feature_importances_, index=X.columns)
feat_imp.nlargest(20).plot(kind='barh', figsize=(10,6))

Эти две модели имеют одинаковую важность функций.

Таким образом, лучший результат, которого мы достигли, - это в среднем менее 60 долларов на ошибку теста RMSE, а модель объясняет 62,4% изменчивости цены листинга на lightGBM.

Результат был хорош? Не хорошо. Однако цену очень сложно смоделировать, и кажется, что в функциях просто не хватает сигнала. Мы можем добавить такие функции, как сезонность, период бронирования, продолжительность пребывания и так далее. Я оставлю это в другой раз.

Блокнот Jupyter можно найти на Github. Наслаждайтесь остатком недели.