Прожив в Нью-Йорке три года ... Если есть что-то, что я принимал как должное, так это то, что цены на жилье и аренду продолжают расти.

После жилищного кризиса 2009 года цены на жилье заметно восстановились, особенно на основных рынках жилья. Однако в 4 квартале 2016 года я с удивлением прочитал, что цены на жилье на Манхэттене в годовом исчислении упали больше всего за последние 4 года. Фактически, средние цены при перепродаже квартир и кооперативов упали на 6,3%, что стало первым падением с первого квартала 2015 года. Снижение частично объясняется политической неопределенностью внутри страны и за рубежом в связи с Брекситом и выборами 2016 года (демократия RIP). .



Возник интересный вопрос: означает ли это, что цены на жилье снова выходят на плато? Понимание того, что входит в прогноз цен на жилье, - хороший первый шаг к ответу на этот вопрос. Чтобы подойти к этой проблеме, мне нужно было работать с набором данных о ценах на жилье.

Kaggle.com - это сайт, посвященный анализу данных и наполненный всевозможными соревнованиями, задачами и наборами данных для изучения. Часто эти данные публикуются теми же компаниями, которым требуется помощь в их анализе. На сайте проводился конкурс, в ходе которого аналитикам данных предлагалось создать модель прогнозирования цен на жилье.



В этом конкурсе участвовали данные о характеристиках домов и ценах на жилье в Эймсе, штат Айова. Это было немного дальше от Манхэттена, чем я ожидал, но это было хорошее место для начала.

Моей целью было:

1) Создайте эффективную модель прогнозирования цен

2) Проверьте точность прогноза модели.

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

Очистка и обработка данных

Первым шагом было импортировать данные и начать понимать, с чем я работаю. Я использовал следующие пакеты в Python для проведения этого анализа:

#Declarations:
import pandas as pd
import numpy as np
import re
from datetime import datetime
from scipy import stats
import random
import sklearn
from sklearn import datasets, linear_model
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
from sklearn.metrics import mean_squared_error
from math import sqrt
from IPython.core.display import Image, HTML
import matplotlib
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
import seaborn as sns
%matplotlib inline

Я импортировал данные в панды:

#Import my Data
#From Kaggle Competition: House Prices: Advanced Regression Techniques
#This is the "training" data set I downloaded from the Kaggle Competition
#https://www.kaggle.com/c/house-prices-advanced-regression-techniques/data
data=pd.read_csv("train.csv", na_filter=False)
data.describe
data.info()
data.head(5)
features= ["Id", "LotArea", "Utilities", "Neighborhood", "BldgType", "HouseStyle", "OverallQual", 
               "OverallCond", "YearBuilt", "YearRemodAdd", "RoofStyle", "RoofMatl", "GrLivArea", 
               "FullBath", "HalfBath", "BedroomAbvGr", "KitchenAbvGr", "MoSold", "YrSold", "SalePrice"]
numfeat= ["Id", "LotArea", "OverallQual", 
               "OverallCond", "YearBuilt", "YearRemodAdd", "GrLivArea", 
               "FullBath", "HalfBath", "BedroomAbvGr", "KitchenAbvGr", "MoSold", "YrSold", "SalePrice"]
catfeat= ["Id", "Utilities", "Neighborhood", "BldgType", "HouseStyle", "RoofStyle", "RoofMatl"]
Rdata=data.loc[:, features]
Rdata.info()
Rdata.head(5)
Rdata.tail(5)
#check if IDs are unique
data.columns

Набор данных содержал следующие атрибуты с 1460 записями. Данные удивительно чистые, без исключения нулевых значений. В этом преимущество использования набора данных Kaggle - я могу сосредоточиться на анализе и интерпретации данных, а не на очистке. Используя тип данных, я смог идентифицировать столбцы числовых и категориальных данных. Последний столбец, SalePrice, - это то, что мы пытаемся предсказать. Прежде чем я построил какие-либо модели для этих данных, я хотел визуализировать данные:

#Quick check, are there numerical relationships we can spot among the numerical data
plotfeats= ["LotArea", "OverallQual", 
               "OverallCond", "YearBuilt", "YearRemodAdd", "GrLivArea", 
               "FullBath", "HalfBath", "BedroomAbvGr", "KitchenAbvGr", "MoSold", "YrSold"]
for f in plotfeats:
    #ax = plt.gca()
    ax=plt.subplots(figsize=(6,3))
    ax=sns.regplot(x=Rdata[f], y=Rdata['SalePrice'])
    plt.show()
sns.heatmap(Rdata.corr())

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

В строке продажной цены (последняя строка) явно есть некоторые переменные, которые имеют более сильную связь с продажной ценой, например, «Общее качество» и «Жилая площадь». Любопытно, что общее состояние и надземная кухня отрицательно коррелируют с ценой.

Затем я визуализировал категориальные данные, используя графики скрипки. Эти графики настолько полезны, потому что они похожи на коробчатые диаграммы, которые показывают диапазон данных, но также включают дополнительный компонент (ширину скрипки), который отображает частоту данных:

#Let's see what type of categorical data we have
catdata=Rdata.loc[:, catfeat]
cat_values={}
for n in catfeat[1:len(catfeat)]:
    print(n)
    print(pd.value_counts(catdata[n]))
    ax = plt.subplots(figsize=(7, 2.5))
    plt.xticks(rotation='vertical')
    ax=sns.violinplot(x=n, y="SalePrice", data=Rdata, linewidth=1)
    plt.show()

Моделирование данных

Теперь, когда мы знаем, с чем работаем, мы можем приступить к построению модели для прогнозирования продажной цены. Поскольку данные настолько чистые и некоторые из переменных, по-видимому, имеют сильную корреляцию с продажной ценой, я хотел начать с реализации модели линейной регрессии. Во-первых, мне нужно было превратить категориальные данные в фиктивные столбцы. Это включает в себя создание столбца для каждого из типов категорий в столбцах категорий и присвоение записи 1 или 0, если категория соответствует. Использование фиктивных переменных позволяет нам изолировать вклад каждого типа категории в категориальных столбцах, потому что мы можем подобрать отдельные бета-значения для каждого типа категории, когда мы подгоняем модель линейной регрессии к набору функций. Это также упрощает последующую интерпретацию бета-версий модели. В Pandas есть встроенная функция, которая заботится о процессе имитации:

#Now I want to turn my categorical variables into dummy variables before I do any regressions
Rdatadum=Rdata
categories = catfeat[1:len(catfeat)]
for category in categories:
    series = Rdatadum[category]
    dummies = pd.get_dummies(series, prefix=category)
    Rdatadum = pd.concat([Rdatadum, dummies], axis=1)
print Rdatadum.columns
Rdatadum.head()

Слева показан полный список столбцов. Например, вы можете видеть, что для каждого района создан отдельный столбец. Прежде чем мы сможем подобрать модель, нам придется удалить исходные столбцы категорий, такие как столбец «Окрестности».

removefeats= ["Id", "Utilities", "Neighborhood", "BldgType", "HouseStyle", "RoofStyle", "RoofMatl", 'SalePrice']
X = Rdatadum.drop(removefeats, axis=1)
y = Rdatadum['SalePrice']
lr = linear_model.LinearRegression()
lr_model = lr.fit(X, y)
y_pred = lr_model.predict(X)
lr_r2 =  r2_score(y, y_pred)
bx=plt.subplots(figsize=(12,5))
bx= sns.barplot(x=0, y=1, data=pd.DataFrame(zip(X.columns, lr_model.coef_)))
plt.xticks(rotation='vertical')
plt.xlabel("Model Coefficient Types")
plt.ylabel("Model Coefficient Values")
plt.show()
print "R squared: ", (lr_r2)
print "Average Coefficients: ", (abs(lr_model.coef_).mean())
print "Root Mean Squared Error: ", sqrt(mean_squared_error(y, y_pred))
ax = sns.regplot(y, y_pred)

Я использовал модель линейной регрессии в пакете SKLearn для соответствия данным. В качестве основы я включил все атрибуты в простую модель без перекрестной проверки. Это график реальной и прогнозируемой продажной цены.



from sklearn.model_selection import cross_val_score
np.mean(cross_val_score(lr_model, X, y, n_jobs=1, cv=5))
0.8191485593927712

Оценка для базовой модели с перекрестной проверкой составляет 81,9% - пока что это довольно хороший предсказатель. Давайте посмотрим, как мы можем улучшить модель. Чтобы уменьшить переоснащение модели, существует два распространенных метода регуляризации, которые называются регуляризацией Лассо и Ридж (или Тихонова). Оба этих метода используют перекрестную проверку, чтобы уменьшить бета-значения регрессионной модели, уступая меньшее значение квадрата r для более высокой предсказательной силы. SKLearn имеет две встроенные функции - LassoCV и RidgeCV, которые помогают подобрать регуляризованные модели линейной регрессии.









XR=X
yR=y
rcv = linear_model.RidgeCV(alphas=
                           (.001, .001, .01, .1, .5, 1,2,3,4,5,6,7,8,9,10),
                           cv=5,
                          )
rcv_model = rcv.fit(XR, yR)
print "Alpha Value: ", (rcv_model.alpha_)
y_predR = rcv_model.predict(XR)
lr_r2 =  r2_score(yR, y_predR)
#lr_r2 = rcv.score(XR, yR)
print "R squared: ", (lr_r2)
print "Average Coefficients: ", (abs(rcv_model.coef_).mean())
print "Root Mean Squared Error: ", sqrt(mean_squared_error(yR, y_predR))
ax = sns.regplot(yR, y_predR)

XL=X
yL=y
lcv = linear_model.LassoCV(alphas=
                           (.001, .001, .01, .1, .5, 1,10,25,30,35,40,41,42,43,44,45,46,47,48,49,50,60,100), cv=5)
lcv_model = lcv.fit(XL, yL)
print "Alpha Value: ", (lcv_model.alpha_)
y_predL = lcv_model.predict(XL)
lr_r2 =  r2_score(yL, y_predL)
print "R squared: ", (lr_r2)
print "Average Coefficients: ", (abs(lcv_model.coef_).mean())
print "Root Mean Squared Error: ", sqrt(mean_squared_error(yL, y_predL))
bx=plt.subplots(figsize=(12,5))
bx= sns.barplot(x=0, y=1, data=pd.DataFrame(zip(XL.columns, lcv_model.coef_)))
plt.xticks(rotation='vertical')
plt.xlabel("Model Coefficient Types")
plt.ylabel("Model Coefficient Values")
plt.show()
ax = sns.regplot(yL, y_predL)
plt.ylabel("Predicted Sale Price")
plt.show()

np.mean(cross_val_score(rcv_model, XR, yR, n_jobs=1, cv=5))
np.mean(cross_val_score(lcv_model, XL, yL, n_jobs=1, cv=5))
Ridge CV Model Score: 0.822158080616
Lasso CV Model Score:  0.822819369116

Сравнивая эти две регуляризованные модели с исходной моделью линейной регрессии, показатель перекрестной проверки улучшился с 81,9% до ~ 82,2%. Это улучшение было достигнуто за счет ограничения значений бета модели: среднее значение бета упало с ~ 24 тыс. В нерегуляризованной модели до ~ 14,5 тыс. И ~ 16,2 тыс. В регуляризованной модели. График бета-коэффициентов для LR1 рядом с LCV1 ясно иллюстрирует этот момент:

Верхняя гистограмма показывает бета-значения для LR1. Метод регуляризации Lasso CV сужает бета-значения и настраивает некоторые из них до нуля. (Обратите внимание на изменение масштаба на рисунке для бета-версии LCV1, достигающей 10 000, в то время как верхняя диаграмма - 20 000). Эти методы регуляризации помогли нам улучшить нашу модель. Для дальнейшего улучшения нашей модели мне нужно было выполнить выбор некоторых функций и разработать функции.

Я попытался изменить функцию, используя 3 раунда выбора функции / eng:

  1. LotArea - это значение, которое, как я ожидаю, будет хорошо коррелировать с продажной ценой - чем больше дом, тем он должен быть дороже, при прочих равных, верно? Однако отношения не были чистыми:

Оказалось, что линейная аппроксимация искажена несколькими отклонениями. Сами данные не были линейными. Я решил удалить все точки LotArea ›50000 и использовать преобразование журнала для переменной LotArea.

Я также решил исключить переменные, которые имели низкую корреляцию с продажной ценой, используя тепловую карту, которую я подготовил ранее, в качестве руководства - Общее состояние, Половина ванны, Месяц продажи, Год продажи. Я также удалил категориальную переменную Utilities, потому что там все записи, кроме одной, попали в категорию утилит «All Pub».

#Let's try some feature engineering to choose the right sample to work on
#RO "remove outliers" for Lot Area
X_RO=X[X['LotArea']<50000]
y2 = Rdatadum[Rdatadum['LotArea']<50000]['SalePrice']
#Convert Lot Area to Log of Lot Area
X_RO['LogLotArea']=X_RO['LotArea'].apply(np.log)
#And try to remove features- 
#numerical features that have low correlation to sale price
#And categorical features that do not have big differences in sale price across categories OR
#do not have significant distribution of samples in all categories
removefeats=['OverallCond' , 'HalfBath', 'MoSold', 'YrSold', 'Utilities_AllPub', 
             'Utilities_NoSeWa', 'LotArea']
X_ROnew=X_RO.drop(removefeats, axis=1)
X_ROnew.head()

2. В дополнение ко всему, что указано в (1), я попытался полностью отбросить переменную LotArea.

X3R=X2R.drop('LogLotArea', axis=1)
y3R=y2R

3. Я вернул журнал переменной LotArea, но я удалил несколько других числовых переменных, которые имели низкую корреляцию с продажной ценой: год постройки, год добавления ремонта, спальня над землей, кухня над землей.

#Let's try removing more features that have low correlation, and bring back Log of Lot Area
removemorefeats=['YearBuilt', 'YearRemodAdd', 'BedroomAbvGr', 'KitchenAbvGr']
X4R=X2.drop(removemorefeats, axis=1)
y4R=y2

После запуска моделей с описанными выше разработками и выбором функций я получил следующие баллы перекрестной проверки:

print "LR model after round (1) of feature selection/engineering: " , np.mean(cross_val_score(lr_model2, X2, y2, n_jobs=1, cv=5))
print "RidgeCV model after round (1) of feature selection/engineering: " ,np.mean(cross_val_score(rcv_model2, X2R, y2R, n_jobs=1, cv=5))
print "LassoCV model after round (1) of feature selection/engineering: ", np.mean(cross_val_score(lcv_model2, X2L, y2L, n_jobs=1, cv=5))
print "RidgeCV model after round (2) of feature selection/engineering: ", np.mean(cross_val_score(rcv_model3, X3R, y3R, n_jobs=1, cv=5))
print "RidgeCV model after round (3) of feature selection/engineering: ", np.mean(cross_val_score(rcv_model4, X3R, y3R, n_jobs=1, cv=5))

Модель LR после раунда (1) выбора характеристик / eng: 0,838734879997
Модель RidgeCV после раунда (1) выбора характеристик / eng: 0,837822313779
Модель LassoCV после раунда (1) выбора характеристик / eng: 0,838073016076
Модель RidgeCV после раунда (2) выбора характеристик / eng: 0,834906701028
Модель RidgeCV после раунда (3) выбора характеристик / eng: 0,835601728922

Наилучшая полученная оценка перекрестной проверки составила 83,9%, LR после раунда (1).

Выводы на основе данных

Мы можем изучить характеристики этой модели:

#Let's examine the best performing model I have so far
vals=[[a[0],a[1]] for a in zip(XL.columns,lr_model2.coef_)]
val=pd.DataFrame(vals, columns=['Feature', 'Beta Value'])
val['Absolute Value']=abs(val['Beta Value'])
val.sort('Absolute Value', ascending=False)

Слева показаны верхние значения бета-тестирования для нашей модели. Удивительно, но материал крыши оказывает огромное влияние на результат нашей модели. По-видимому, именно эти характеристики дома имеют решающее значение для создания точной модели. Соседство также кажется очень влиятельной категорией. Когда мы изучаем зависимости соседства от продажной цены на скрипке, становится очевидным, что у NoRidge, NridgeHt и StoneBr самый высокий диапазон цен, с самыми большими максимальными ценами. Это отражено в значении бета тех трех районов, которые имеют самые высокие значения бета по сравнению с остальными районами.

По количеству продаж в каждом районе NAmes, CollgCr и OldTown замыкают тройку лидеров.

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

test=pd.read_csv("test.csv", na_filter=False)
test.columns
Rtest=test.loc[:, features]
Rtest.columns
Rtestdum=Rtest
categories = catfeat[1:len(catfeat)]
for category in categories:
    series = Rtestdum[category]
    dummies = pd.get_dummies(series, prefix=category)
    Rtestdum = pd.concat([Rtestdum, dummies], axis=1)
#print Rtestdum.columns
Rtestdum.head()
# print Rtestdum['HouseStyle'].value_counts()
# print Rdatadum['HouseStyle'].value_counts()
# print Rtestdum['RoofMatl'].value_counts()
# print Rdatadum['RoofMatl'].value_counts()
removefeats= ["Id", "Utilities", "Neighborhood", "BldgType", "HouseStyle", "RoofStyle", "RoofMatl", 'SalePrice']
Xtest = Rtestdum.drop(removefeats, axis=1)
Xtest['LogLotArea']=Xtest['LotArea'].apply(np.log)
removefeats2=['OverallCond' , 'HalfBath', 'MoSold', 'YrSold', 'Utilities_AllPub', 'Utilities_NA', 'LotArea']
Xtest=Xtest.drop(removefeats2, axis=1)
Xtest['HouseStyle_2.5Fin']=Xtest['OverallQual']*0
Xtest['RoofMatl_Membran']=Xtest['OverallQual']*0
Xtest['RoofMatl_Metal']=Xtest['OverallQual']*0
Xtest['RoofMatl_ClyTile']=Xtest['OverallQual']*0
Xtest['RoofMatl_Roll']=Xtest['OverallQual']*0
Xtest['Utilities_NoSeWa']=Xtest['OverallQual']*0
Xtest=Xtest.reindex(columns=list(X2.columns))
ytest=lcv_model2.predict(Xtest)
sns.distplot(ytest)
yy=pd.DataFrame(ytest)
yy['SalePrice']=yy.pop(0)
yy['Id']=Rtestdum['Id']
yy.set_index('Id', inplace=True)
yy.to_csv('submission3.csv')

Распределение прогнозов по тестовой выборке выглядит так:

Пришло время отправить эти данные в официальную таблицу лидеров Kaggle:

Вывод

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

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

Работа здесь - только часть 1 этого ценового проекта продажи жилья. Надежда на следующие шаги состоит в том, чтобы построить модель, настроенную на цены на жилье на Манхэттене или Нью-Йорке, чтобы мы могли глубже погрузиться в прогнозирование будущих цен на жилье и понять, когда или если на горизонте видится жилищный кризис. Быть в курсе.