title поиск квартиры часть 2

Введение

Данный пост является одним из серии постов описывающих обработку данных с использованием Pandas,Numpy и иных средств визуализации/анализа данных.

Disclaimer(отказ от ответственности)

Данная информация находится в первом посте этой серии и рекомендуется ознакомится с ней

Pre-requierments

Для работы с используемым набором данных(здесь и далее - датасет(dataset)) на данном этапе будет производится отсев лишних признаков с использованием эвристик из Scikit-learn , а также Matplotlib

1
2
3
4
5
6
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.feature_selection import RFECV
from collections import Counter
from sklearn.feature_selection import SelectKBest, f_classif

Подготовка к отбору

Текущий датасет, который мы взяли из первого поста серии имеет примерно следующий вид

0 2 5 16 17
cost 4800000 3200000 2500000 4670000 3199000
district 1 1 1 1 1
exterior 0 0 1 1 0
quickly 0 0 0 0 0
repair 1 0 0 0 1
total_area 50 46 43 52 46
type_house 0 0 -0.3 0 -0.2
walls -0.1 0 0 0 -0.4
wc 1 0 1 1 0
year 2010 1959 1960 1988 1969
distance 6 4 6 4 1
last_floor False False False False False
first_floor False False False False True

Содержит он 12 признаков, некоторые из которых оказывают явное влияние на формирование цены квартиры, а другие бесполезны чуть более чем полностью. Но как отделить зерна от плевел? К счастью тут всё придумали до нас, и в пакете Scikit-learn есть достаточно инструментария, для того чтобы найти ответ на вопрос заданный ранее.
Но сперва - разделим нашу выборку на обучающую и тестовую.

1
2
#30% for test selection
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

И также проведем простейшую нормализацию данных, поскольку в нашем датасете содержатся данные которые превышают границы “привычного” нормального распределения, и которые желательно свести к нем(например год постройки здания)

1
2
3
#Make our data nomrmal, again!
x_train = (x_train - x_train.mean()) / (x_train.max() - x_train.min())
x_test = (x_test - x_test.mean()) / (x_test.max() - x_test.min())

Число признаков

Первый вопрос который можно задать себе - “сколько признаков из тех что есть - нам нужны?”.Ответ может предоставить классификатор использующий метод случайного леса(RandomForestClassifier) с использование рекурсивного перебора атрибутов и кросс-валидацией(RFECV )

1
2
3
forest = RandomForestClassifier()
rfecv = RFECV(estimator=forest, step=1, cv=5, scoring='accuracy')
rfecv = rfecv.fit(x_train, y_train)

После чего отобразим график “важности” количества признаков для нашего набора

1
2
3
4
5
plt.figure()
plt.xlabel("Number of features selected")
plt.ylabel("Cross validation score of number of selected features")
plt.plot(range(1, len(rfecv.grid_scores_) + 1), rfecv.grid_scores_)
plt.show()

title важность признаков

Как видно из текущего рисунка - наиболее полно характеризует нашу выборку всего 6 признаков. Осталось их сохранить и отделить лишние, чтоб они не искажали работу модели для предсказания.

Учет признаков

Но прежде чем слепо верить результатам всего одного классификатора - прибегнем к использованию другого классификатора(KBest), который также нацелен на отбор признаков который “наилучшим” образом характеризуют модель.

1
2
3
selector = SelectKBest(f_classif)
selector.fit(x_train, y_train)
scores = selector.scores_

И также посмотрим какие признаки отобрал данный классификатор

1
2
3
4
5
6
7
8
indices = np.argsort(scores)[::-1]
plt.figure()
plt.title("Feature importance for K-Best")
plt.bar(range(x_train.shape[1]), scores[indices], align="center")
col_names = [x_train.columns[x] for x in indices]
plt.xticks(range(x_train.shape[1]), col_names)
plt.xlim([-1, x_train.shape[1]])
plt.show()

Получим новую гистограмму которая описывает “важность” того или иного признака модели

title важность признаков

Общие признаки.

Теперь можно совместить признаки которые выбрал RandomForestClassifier и Kbest. Идея выглядит как своеобразное “голосование”, мы просто агрегируем всю информацию и выберем признаки за которые “проголосовали” оба классификатора.

1
2
3
4
5
6
7
8
9
10
11
12
13
num_of_feat = rfecv.n_features_ # Num of important features
count = Counter()
#Put in counter RFECV-results
for col in x_train.columns[rfecv.support_]:
count.update([col])
#Put in counter KBest-results
most_popular = col_names[:num_of_feat]
count.update(most_popular)
#filter coomon decision
cols = [feature for feature,frequency in count.items() if frequency==2]

В итоге получилось получился список(cols) состоящий из 5 признаков - [‘district’, ‘repair’, ‘total_area’, ‘year’, ‘distance’] которые посчитали важными оба классификатора

Уменьшение размерности выборки

Дело осталось за малым - просто отбросим все излишние признаки в нашем датасете, вернее в той части что отражает его признаки

1
X = X[cols]

И можно приступать к задаче предсказания цены на основе оставшихся признаков. Но,это уже тема совсем другого поста…