Ответ на этот вопрос дает интересные идеи, которые могут принести пользу хозяину, стремящемуся максимизировать свою прибыль. Чтобы глубже погрузиться в возможные факторы, влияющие на стоимость аренды Airbnb, я использовал различные модели линейной регрессии с Scikit-Learn и StatsModels в Python . В этом посте я выделю подход, который я использовал для ответа на этот вопрос, а также то, как я использовал две популярные модели линейной регрессии.

Главный вопрос: Что предсказывает стоимость аренды Airbnb?

Данные: набор данных Kaggle о ~ 74 тыс. взятых напрокат Airbnb. Набор данных включает в себя множество функций, таких как: количество кроватей, количество разрешенных гостей, описание, количество отзывов и многое другое. Для более подробного предварительного просмотра функций и данных см. Набор данных здесь.

Подход / модели: Этот вопрос относится к категории регрессии и прогнозирования, поэтому были использованы модели линейной регрессии. Я использовал StatsModels для создания начальной точки модели обыкновенных наименьших квадратов и Scikit-Learn для создания модели LassoCV.

Шаг 0: подумайте о проблеме и наборе данных

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

Шаг 0: Импортируйте пакеты

# Import packages
import pandas as pd
import patsy
import statsmodels.api as sm
import statsmodels.formula.api as smf
import statsmodels.api as sm
from statsmodels.stats.outliers_influence import variance_inflation_factor
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.model_selection import train_test_split
from sklearn.linear_model import lars_path
from sklearn.linear_model import LinearRegression, Lasso, LassoCV
from sklearn.metrics import r2_score
import scipy.stats as stats
# Visualization
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

Шаг 1: Исследовательский анализ данных и подготовка модели в Pandas

Ниже приведены некоторые общие шаги по очистке данных и EDA, которые я всегда предпринимаю. Я не включил некоторые из более тонких шагов, относящихся к конкретному набору данных.

  • Я начал с pd.describe () и pd.info (), чтобы понять, какие данные отсутствуют, и значения для каждой функции.
  • Убраны имена функций, в которых были пробелы.
  • Сочетал некоторые функции со многими категориями. Например, «property_type» имеет 25 различных категорий (это проверяется с помощью pd.value_counts ()). Я объединил самые низкие 23 в категорию «других». Это было сделано и для нескольких других функций. Это помогло, потому что у меня не было более 20 новых столбцов, созданных из фиктивного кода.
  • Исключены функции, которые были распределены очень неравномерно или не будут использоваться. Например, «host_has_profile_pic» содержит 32 тысячи «да» и только 97 «нет». Это не пригодится в модели.
  • Дальнейшее изучение недостающих данных. Для функций, которые я считал важными (например, «review_scores_rating»), я ввел медианное значение. Примечание - здесь интерполяция могла быть более тонкой, чем заполнение медианной. Например, я мог бы заполнить каждую подкатегорию аренды (квартира, дом, другое) своей собственной медианой.
  • Проверяйте корреляции с помощью pandas (pd.corr ()) и визуально с помощью тепловой карты Seaborn: между функциями и результатом, а также между самими функциями.
model_df.corr() # Whole correlation matrix
model_df.corr()['log_price'] # Check correlations with outcome only
sns.heatmap(model_df.corr(), cmap="seismic", annot=True, vmin=-1, vmax=1);
  • Фиктивный код различных категориальных переменных (например, «cancellation_policy», «property_type»). Обязательно установите drop_first = True, чтобы столбец ссылки не был включен. Примечание. Если функция является категориальной, но имеет только два варианта (например, «Мужской», «Женский»), нет необходимости вводить фиктивный код вручную, просто убедитесь, что это целое число (0,1).
# Example dummy coding for 'cancellation_policy'
model_df = pd.get_dummies(model_df, columns=['cancellation_policy'], drop_first=True)
  • Убедитесь, что целевая переменная (log_price): нормально распределена, эксцесс и асимметрия нормальные.
sns.distplot(model_df['log_price'], kde=True,);
fig = plt.figure()
res = stats.probplot(model_df['log_price'], plot=plt)
print("Skewness: %f" % model_df['log_price'].skew())
print("Kurtosis: %f" % model_df['log_price'].kurt())

Шаг 2. Запустите OLS в StatsModels и проверьте предположения линейной регрессии.

Модель OLS в StatsModels предоставит нам простейшую (нерегуляризованную) модель линейной регрессии, на которой будут строиться наши будущие модели. Всегда хорошо начать с простого, а затем добавить сложности. Кроме того, он предоставляет красивую сводную таблицу, которую легко интерпретировать. Это отличное место для проверки предположений линейной регрессии.

  • Создайте матрицу характеристик с Пэтси. Пэтси хорош тем, что создает нашу модель в простом синтаксисе R. По умолчанию он также добавляет точку пересечения в вашу модель (если вы не используете Пэтси, обязательно добавьте точку пересечения вручную).
  • Тренировка / тест разделяют данные. 80% будут обучаться, а 20% - тестировать (подробнее о том, почему нет разделения на валидацию, позже).
  • Запустите модель OLS, изучите сводную таблицу.

Ключевые показатели для изучения из сводной таблицы OLS:

  • Скорректировано R². Сообщает вам, какое отклонение в вашем результате (цене аренды) объясняется предикторами модели.
  • Log-Likelihood: насколько вероятно, что эта модель будет работать так же с новыми данными. Вы хотите, чтобы это число было близко к нулю (скорее всего, оно будет в значительной степени отрицательным).
  • Значения P: проверьте каждую переменную. Менее 0,05 является стандартным порогом, чтобы дать вам представление о том, какие из них вносят существенный вклад в модель.
  • Дурбин-Ватсон: есть ли автокорреляция? Это должно быть около двух.
  • Также можно проверить Омнибус и Номер условия, чтобы получить общее представление о качестве модели.

Получите коэффициенты инфляции дисперсии (VIF)

  • VIF не создаются из приведенной выше таблицы OLS, поэтому их следует извлекать вручную.
  • Это отличный способ проверить мультиколлинеарность вашей модели. Мультиколлинеарность - это когда между вашими функциями существует высокая корреляция. Это предположение линейной регрессии, что ваши данные не имеют мультиколлинеарности, поэтому обязательно проверьте это. Вы хотите, чтобы ваши VIF были меньше 4.
# Check for VIFs of each feature, then save to its own DF
vif_census = pd.DataFrame()
vif_census[“VIF Factor”] = [variance_inflation_factor(X_census.values, i) for i in range(X.shape[1])]
vif_census[“features”] = X_census.columns

Проверьте остатки модели

  • Другое предположение линейной регрессии состоит в том, что остатки распределены нормально. Вы легко можете проверить это с помощью сюжета. На графике ниже вы можете видеть, что есть некоторый спред (особенно вверху слева), но остатки в основном попадают в среднюю линию.
# Use statsmodels to plot the residuals vs the fitted values
plt.figure(figsize=(12,8))
plt.scatter(fit_census.predict(), fit_census.resid); # print resids vs predictions
plt.title("Residuals plot from OLS Model")
plt.xlabel("Predicted Values")
plt.ylabel("Residuals")
plt.savefig('LR_Residual_Plot')

Шаг 3: LassoCV

Затем я использовал регуляризованные модели линейной регрессии. Эти типы моделей применяют штрафы к переменным и помогают снизить сложность моделей. Регуляризация помогает найти компромисс между смещением и дисперсией и помогает модели более обобщать для новых тестовых данных. См. Эту статью для хорошего обзора регуляризации.

  • Поскольку регуляризованные модели наказывают переменные, нам необходимо стандартизировать их, чтобы все они наказывались с одинаковой скоростью. Это делается путем применения к данным стандартного скейлера. Думайте о стандартизации, как о размещении всех функций в одной плоскости и присвоении им оценки z.
# Standard scale the data
std = StandardScaler()
std.fit(X_train_census.values) # only std.fit on train set
X_tr_census = std.transform(X_train_census.values)
X_te_census = std.transform(X_test_census.values)

Краткое примечание о типах моделей

  • Теперь мы готовы запустить наши регуляризованные модели. Мы хотим использовать Lasso, Ridge, ElasticNet или все три? Я использую здесь только Лассо и не буду вдаваться в подробности, но Лассо и Ридж по-разному наказывают переменные / коэффициенты, поэтому каждый из них может использоваться для получения разных идей. Вкратце, Лассо настолько штрафует некоторые коэффициенты, что они становятся равными нулю и исключаются из модели. По сути, это автоматический выбор функции. Ридж наказывает коэффициенты, «уменьшая» их и «размазывая» их влияние. Ridge хорош, когда вы хотите плавно справиться с мультиколлинеарностью. ElasticNet по сути представляет собой комбинацию обоих. На сайте Scikit-learn есть хорошая документация по всем этим вопросам. Код по сути одинаков для всех трех.

Шаг 3а: LassoCV

  • Я использовал CV-версию Lasso, потому что они имеют встроенную перекрестную проверку. Это означает, что вам не нужно вручную проверять новый набор, а модель CV выполняет обучение и перекрестную проверку за вас. Заметьте ранее (код разделения поезд-тест), что я сделал только разделение на 80–20 - никаких данных для проверки не было отложено, скорее я скармливаю моделям CV все 80 для обучения и проверки.
  • Мне нравится сначала запускать LassoCV из-за встроенного выбора функций. Функции, которые выходят из модели с коэффициентом бета 0,0, вероятно, не являются сильными характеристиками и могут быть удалены позже.
# Run the cross validation, find the best alpha, refit the model on all the data with that alpha
alphavec = 10**np.linspace(-2,2,200)
lasso_model_census = LassoCV(alphas = alphavec, cv=5)
lasso_model_census.fit(X_tr_census, y_train_census)
  • В приведенном выше коде я сделал три вещи: 1) создал массив альфа-каналов, который будет помещен в модель CV 2) Инициируйте модель с параметром альфа, установленным в альфа-массив шага 1, и установил пятикратную перекрестную проверку 3) Подгонка данные обучения.
  • После этого шага мы можем изучить бета-коэффициенты для каждой функции, а также получить доступ к важной информации о модели.

Краткое примечание о показателях сравнения моделей

  • Есть несколько способов сравнить регрессионные модели, но здесь я использовал R² и среднюю абсолютную ошибку (MAE). R² - это мера того, насколько вариативна переменная результата, которую мы учитываем в нашей модели. MAE - это мера средней ошибки в нашей модели. Его легко интерпретировать, и это отличный показатель, которым можно поделиться. Например, в моих моделях, прогнозирующих стоимость аренды Airbnb, если мой MAE составляет ($) 20, то при прогнозировании я могу сказать, что моя модель отклоняется примерно на 20 долларов.
# Print feature name zipped with its beta
lasso_betas = list(zip(X_train_census.columns,
lasso_model_census.coef_))
# R2 of Training set
lasso_model_census.score(X_tr_census,y_train_census)

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

# Predict model on test data
y_census_pred = lasso_model_census.predict(X_te_census)
# R2 of test set using this model
r2_score(y_test_census, y_census_pred)
#Mean Absolute Error (MAE)
def mae(y_true, y_pred):
    return np.mean(np.abs(y_pred - y_true))
mae(y_test_census, y_census_pred)
# Plot
plt.scatter(y_test_census, y_census_pred)
plt.plot([0,10],[0,10],color='red')
plt.grid(True)
plt.title('Predicted vs. Actual Rental Price (log) with LASSO CV')
plt.ylabel('Rental Price (log) Predicted')
plt.xlabel('Rental Price (log) Actual');

  • Здесь мы видим, что наша модель неплохая, с некоторыми невысокими характеристиками при высоких ценах на аренду. Вероятно, это потому, что их не так много в этом диапазоне.

Использование пути LARS для определения наиболее важных функций

  • Путь LARS - хороший инструмент, чтобы увидеть, какие функции наиболее / наименее важны в вашей модели LassoCV. Проще говоря, на рисунке ниже характеристики, которые сначала изменяются с нуля (положительные или отрицательные), являются наиболее важными, а те, которые становятся ненулевыми в последнюю очередь, являются наименее важными. То есть особенности, которые появляются слева, являются наиболее важными. Это также отражено в коэффициентах бета для этих функций.
print("Computing regularization path using the LARS ...")
alphas, _, coefs = lars_path(X_tr_census, y_train_census, method='lasso')
# # plotting the LARS path
xx = np.sum(np.abs(coefs.T), axis=1)
xx /= xx[-1]
plt.figure(figsize=(10,10))
plt.plot(xx, coefs.T)
ymin, ymax = plt.ylim()
plt.vlines(xx, ymin, ymax, linestyle='dashed')
plt.xlabel('|coef| / max|coef|')
plt.ylabel('Coefficients')
plt.title('LASSO Path')
plt.axis('tight')
plt.legend(X_train_census.columns)
plt.show()

Напомним, здесь я рассмотрел некоторые из основных шагов, которые мне нравится делать при исследовании и очистке данных (можно сделать гораздо больше, см. Эту статью для получения более подробных советов по EDA). Кроме того, я выполнил простую регрессию OLS и регуляризованную модель лассо. После этих шагов можно запустить RidgeCV и / или ElasticNetCV и сравнить модели.

Спасибо, что прочитали, и дайте мне знать, если мне есть что добавить.