<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Перейдем к подготовке и визуализации данных
Step1:  Приведем данные в порядок
Step2:  Посмотрим процент пропущенных данных по признакам
Step3:  Число уникальных значений для этих колонок
Step4:  Итак, нужно избавить данные от пропусков. Сопоставим информацию и составим план действий.
Первое, что я сделаю - выброшу признак Tel, по заданию его надо было спарсить - я спарсила, но он бесполезен для будущей модели + содержит 46% nanов, не вижу никакого смысла с ним возиться.
Второе
Step5:  Nfloors
Step6:  Metrdist, Walk
Step7:  Учитывая, что средняя скорость пешехода 4 км/ч, а машины 50 км/ч, заполним столбец со значениями предполагаемых километров до метро (помним, что metrdist содержит информацию в минутах)
Step8:  Посмотрим процент странных значений и выбросим эти данные, если он будет мал
Step9:  Посчитаем среднее значение по округу, заполним пропуски
Step10:  Для понимания того, как работать с Kitsp и Livsp, изучим обязательное поле Totsp. Посмотрим на минимум и максимум, посмотрим на распределение.
Step11:  Посмотрю на то, что кроется за краевыми значениями
Step12:  Бывают квартиры общей площадью 11 м.кв., например. Во-первых, по фотографиям видно, что это не так, во-вторых, есть некоторые закрепленные нормы на минимальную площадь жилого помещения, и это около 20 кв.м. В-третьих, странных квартир мало, так что я их просто удалю.
Также удалю слишком большие квартиры, если их будет достаточно мало, так как для наших данных они скорее представляют выбросы. И вряд ли мы сможем нормально прогнозировать цену на элитное жилье с таким датасетом, не будем прыгать выше головы (и заодно позаботимся об уменьшении ошибки будущих моделей)
Step13:  Что теперь можно делать с этой информацией? Нам надо заполнить пропуски в Kitsp и Livsp. Можно предположить линейную зависимость этих параметров от Totsp и попробовать использовать линейную модель для заполнения nanов. Да, линейная модель предскажет значения с ошибками, но если заполнять пропуски без нее, то ошибки тоже будут. А у нее они будут наверняка поменьше, чем у какого-нибудь обычного заполнения медианой.
Step14:  Посмотрим на получившиеся распределения
Step15:  Снова удалим маленький процент странных данных
Step16:  Теперь изучим данные на общую адекватность. Начнем с распределения цен.
Step17:  Так данные выглядеть не должны. Сначала уберу outliers, при построении модели попробую поработать с логарифмом от цены.
Step18:  Изучим данные по этажам
Step19:  Номер этажа нигде не больше этажности дома, уже неплохо)
Step20:  Не понятно наличие хвоста. Посмотрим на него
Step21:  Это реальные данные, но их мало и они сильно портят распределение, уберем их
Step22:  Так лучше. Посмотрим на распределение Nfloors
Step23:  Аналогично
Step24:  Теперь посмотрим на распределение числа комнат
Step25:  Теперь на распределение расстояний до центра города
Step26:  Снова изучим хвост ( > 28 )
Step27:  Вот здесь уже появляется странная информация. Например, реальный адрес по Одинцовскому району Московской области, но при этом указано м. Парк Победы. Да, от него наверное удобно добираться, если ехать из Москвы. Но это не отменяет того, что на самом деле поездка такая будет ИЗ Москвы и уже в Подмосковье.
Или еще пример - 10 минут на машине от м. Алтуфьево, мкр. Северный. Открываем карту - это Северный мкр. Домодедово. При чем тут Алтуфьево? При чем тут Москва?
В общем квартиры по-настоящему далеко. Их мало. Они разные (есть Рублевское направление, есть Домодедово). Обучиться на них хорошо предсказывать цену на удаленные от центра квартиры мы не сможем. Так что я хочу определить максимальную удаленность границы рассматриваемых округов от центра города и оценить объем данных, не проходящих этот порог.
Step28:  Прикинув по карте, получила 27. Не проходящих порог данных меньше 1%, удаляю их.
Step29:  Снова посмотрим на распределение
Step30:  Стало лучше.
Теперь поищем корреляции в данных, построим немного графиков.
Step31:  Видна положительная корреляция цены с площадями и числом комнат, а также отрицательная с расстоянием до центра. Выглядит понятно
Посмотрим на зависимость цены от количества комнат
Step32:  Чем больше комнат, тем больше цена, многокомнатных меньше, чем других вариантов
Теперь построим аналогичный график для расстояний от центра города
Step33:  Чем больше расстояние до центра, тем меньше цена
Теперь посмотрим на зависимость цены от общей площади
Step34:  Чем больше площадь, тем больше цена
И напоследок посмотрим на зависимость цены от округа
Step35:  С большим отрывом лидирует ЦАО
Вспомним, что хотели сделать one hot encoding для некоторых признаков, выбросим после этого их и ссылки
Step36:  И сохраним данные. Остальное в следующем ноутбуке
<ASSISTANT_TASK:>
Python Code:
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns
plt.style.use('bmh')
%matplotlib inline
import random
random.seed(42)
np.random.seed(42)
districts = {1: 'NW', 4: 'C', 5:'N', 6:'NE', 7:'E', 8:'SE', 9:'S', 10:'SW', 11:'W'}
data = pd.read_csv('cian_full_data.csv')
data.head()
Explanation: Перейдем к подготовке и визуализации данных
End of explanation
data.drop(0, axis = 0, inplace = True)
data.head()
data.index = [x for x in range(len(data.index))]
data.rename(columns={'0' : 'Bal', '1': 'Brick', '2': 'Distance', '3': 'Floor', '4': 'Kitsp', '5': 'Livsp', '6': 'Metrdist', 
                     '7': 'New', '8': 'Nfloors', '9': 'Price', '10': 'Rooms', '11': 'Tel', '12': 'Totsp', 
                     '13': 'Walk', '14': 'link'}, inplace = True)
data.head()
data.shape
Explanation: Приведем данные в порядок: удалим первую нулевую строку, восстановим индексы, переименуем колонки
End of explanation
def nans_appearance(data):
    
    columns = []
    nans = data.isnull()
    for i in data.columns:
        x = nans[nans[i] == True].shape[0]
        if (x > 0):
            columns.append(i)
            print('nans in', i, ':', x, '; percentage : '"%.3f"   %(x/data.shape[0]))
            
            
    return columns
col_nans = nans_appearance(data)
Explanation: Посмотрим процент пропущенных данных по признакам
End of explanation
def my_nunique(data, columns):
    for i in columns:
        print(str(i) + ' : ' + str(data[i].nunique()))
my_nunique(data, col_nans)
Explanation: Число уникальных значений для этих колонок
End of explanation
data.drop('Tel', axis = 1, inplace = True)
Explanation: Итак, нужно избавить данные от пропусков. Сопоставим информацию и составим план действий.
Первое, что я сделаю - выброшу признак Tel, по заданию его надо было спарсить - я спарсила, но он бесполезен для будущей модели + содержит 46% nanов, не вижу никакого смысла с ним возиться.
Второе: объектов, для которых неизвестно число этажей, мало - всего 0.006 от объема данных. Я эти 34 объекта тоже выкину.
Третье: есть признаки с малым числом возможных значений (Bal : 5 + nan = 6; Brick : 2 + nan = 3), для них я сделаю one hot encoding. Но позже (после изучения корреляций). Эта же участь постигнет категориальный Distr.
Четвертое: почему в предыдущий пункт не попал признак Walk? Потому что он по смыслу идет в совокупности с Metrdist. Для них я сделаю новый признак Metrokm - расстояние до метро в километрах, опираясь на известные средние скорости пешехода и автомобиля, и уже его заполню средним по округу. После признаки Walk и Metrdist тоже выброшу, как уже не нужные.
Пятое: Kitsp и Livsp. Пропуски в них можно компенсировать засчет поля Totsp, но для принятия какого-то решения надо для начала убедиться в адекватности находящихся в нем данных.
Tel
End of explanation
data[data['Nfloors'].isnull()].shape
data = data[data['Nfloors'] > 0]
data.index = [x for x in range(len(data.index))]
data.shape
Explanation: Nfloors
End of explanation
data['Metrokm'] = np.nan
Explanation: Metrdist, Walk
End of explanation
for i in range(data.shape[0]):
    if (data['Walk'][i] == 1.):
        data['Metrokm'][i] = 4/60 * data['Metrdist'][i]
    elif (data['Walk'][i] == 0.):
        data['Metrokm'][i] = 5/6 * data['Metrdist'][i]
Explanation: Учитывая, что средняя скорость пешехода 4 км/ч, а машины 50 км/ч, заполним столбец со значениями предполагаемых километров до метро (помним, что metrdist содержит информацию в минутах)
End of explanation
data[data['Metrokm'] > 17].shape[0]/data.shape[0]
data = data[(data['Metrokm'] <= 17) | (data['Metrokm'].isnull())]
data.index = [x for x in range(len(data.index))]
Explanation: Посмотрим процент странных значений и выбросим эти данные, если он будет мал
End of explanation
metrokms_distr=[]
for i in districts.values():
    tmp = data[data['Distr'] == i]
    med = tmp['Metrokm'].median()
    
    metrokms_distr.append(med)
tmp = []
ind = 0
for i in districts.keys():
    tmp.append((districts.get(i), metrokms_distr[ind]))
    ind += 1
    
tmp = dict(tmp)
tmp
for i in districts.values():
    for j in range(data.shape[0]):
        if (data['Distr'][j] == i):
            if (not(data['Metrokm'][j] >= 0)):
                value = tmp.get(i)
                data['Metrokm'][j] = value
data.drop(['Metrdist', 'Walk'], axis = 1, inplace = True)
nans_appearance(data)
Explanation: Посчитаем среднее значение по округу, заполним пропуски
End of explanation
totsps = data['Totsp'].unique()
totsps.sort()
print('min =', totsps.min(), '; max =', totsps.max())
count_totsps = []
for i in totsps:
    count_totsps.append(data[data['Totsp'] == i].shape[0])
plt.hist(data['Totsp'], color='r')
plt.show()
Explanation: Для понимания того, как работать с Kitsp и Livsp, изучим обязательное поле Totsp. Посмотрим на минимум и максимум, посмотрим на распределение.
End of explanation
data[data['Totsp'] < 18]['link']
Explanation: Посмотрю на то, что кроется за краевыми значениями
End of explanation
data[data['Totsp'] < 20].shape[0]/data.shape[0]
data = data[data['Totsp'] >= 20]
data.index = [x for x in range(len(data.index))]
data[data['Totsp'] > 320].shape[0]/data.shape[0]
data = data[data['Totsp'] < 320]
data.index = [x for x in range(len(data.index))]
plt.hist(data['Totsp'], color='r')
plt.show()
Explanation: Бывают квартиры общей площадью 11 м.кв., например. Во-первых, по фотографиям видно, что это не так, во-вторых, есть некоторые закрепленные нормы на минимальную площадь жилого помещения, и это около 20 кв.м. В-третьих, странных квартир мало, так что я их просто удалю.
Также удалю слишком большие квартиры, если их будет достаточно мало, так как для наших данных они скорее представляют выбросы. И вряд ли мы сможем нормально прогнозировать цену на элитное жилье с таким датасетом, не будем прыгать выше головы (и заодно позаботимся об уменьшении ошибки будущих моделей)
End of explanation
data.shape
train = data[data['Kitsp'] > 0]
train.shape
test = data[data['Kitsp'].isnull()]
test.shape
X = train['Totsp']
X = X.reshape((X.shape[0],1))
Y = train['Kitsp']
Y = Y.reshape((Y.shape[0],1))
X_test = test['Totsp']
X_test = X_test.reshape((X_test.shape[0],1))
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
Xtr, Xval, Ytr, Yval = train_test_split(X, Y, test_size = 0.2)
model_kitchen = LinearRegression(normalize = True)
model_kitchen.fit(Xtr, Ytr)
pred = model_kitchen.predict(Xval)
from sklearn.metrics import mean_squared_error
from math import sqrt
print('rmse:', sqrt(mean_squared_error(Yval, pred)))
Y_test = model_kitchen.predict(X_test)
ind = 0
for i in range(data.shape[0]):
    if (not(data['Kitsp'][i] >= 0)):
        data['Kitsp'][i] = Y_test[ind]
        ind += 1
nans_appearance(data)
train = data[data['Livsp'] > 0]
train.shape
test = data[data['Livsp'].isnull()]
test.shape
X = train['Totsp']
X = X.reshape((X.shape[0],1))
Y = train['Livsp']
Y = Y.reshape((Y.shape[0],1))
X_test = test['Totsp']
X_test = X_test.reshape((X_test.shape[0],1))
Xtr, Xval, Ytr, Yval = train_test_split(X, Y, test_size = 0.1)
model_liv = LinearRegression()
model_liv.fit(Xtr, Ytr)
pred = model_liv.predict(Xval)
print('rmse:', sqrt(mean_squared_error(Yval, pred)))
Y_test = model_liv.predict(X_test)
ind = 0
for i in range(data.shape[0]):
    if (not(data['Livsp'][i] >= 0)):
        data['Livsp'][i] = Y_test[ind]
        ind += 1
Explanation: Что теперь можно делать с этой информацией? Нам надо заполнить пропуски в Kitsp и Livsp. Можно предположить линейную зависимость этих параметров от Totsp и попробовать использовать линейную модель для заполнения nanов. Да, линейная модель предскажет значения с ошибками, но если заполнять пропуски без нее, то ошибки тоже будут. А у нее они будут наверняка поменьше, чем у какого-нибудь обычного заполнения медианой.
End of explanation
plt.hist(data['Kitsp'], color = 'r')
plt.show()
plt.hist(data['Livsp'], color = 'r')
plt.show()
Explanation: Посмотрим на получившиеся распределения
End of explanation
data[data['Kitsp'] < 4].shape
data = data[data['Kitsp'] >= 4]
data.index = [x for x in range(len(data.index))]
data[data['Livsp'] < 10].shape
data = data[data['Livsp'] >= 10]
data.index = [x for x in range(len(data.index))]
nans_appearance(data)
Explanation: Снова удалим маленький процент странных данных
End of explanation
print('min: ', data['Price'].min(), '; max:', data['Price'].max())
plt.hist(data['Price'], color = 'r')
plt.show()
Explanation: Теперь изучим данные на общую адекватность. Начнем с распределения цен.
End of explanation
data[data['Price'] < 1500000].shape[0]
data.drop(data[data['Price'] < 1500000].index, inplace = True)
data.index = [x for x in range(len(data.index))]
data[data['Price'] > 200000000].shape[0]/data.shape[0]
data.drop(data[data['Price'] > 200000000].index, inplace = True)
data.index = [x for x in range(len(data.index))]
plt.hist(data['Price'], color = 'r')
plt.show()
Explanation: Так данные выглядеть не должны. Сначала уберу outliers, при построении модели попробую поработать с логарифмом от цены.
End of explanation
data[data['Nfloors'] < data['Floor']].shape
Explanation: Изучим данные по этажам
End of explanation
plt.hist(data['Floor'], color = 'r')
plt.show()
Explanation: Номер этажа нигде не больше этажности дома, уже неплохо)
End of explanation
tmp = data[data['Floor'] > 40]
tmp[['Floor', 'link']]
Explanation: Не понятно наличие хвоста. Посмотрим на него
End of explanation
data.drop(data[data['Floor'] > 40].index, inplace = True)
data.index = [x for x in range(len(data.index))]
plt.hist(data['Floor'], color = 'r')
plt.show()
Explanation: Это реальные данные, но их мало и они сильно портят распределение, уберем их
End of explanation
plt.hist(data['Nfloors'], color = 'r')
plt.show()
data[data['Nfloors'] > 50].shape[0]/data.shape[0]
Explanation: Так лучше. Посмотрим на распределение Nfloors
End of explanation
data.drop(data[data['Nfloors'] > 50].index, inplace = True)
data.index = [x for x in range(len(data.index))]
plt.hist(data['Nfloors'], color = 'r')
plt.show()
Explanation: Аналогично
End of explanation
rooms = data['Rooms'].unique()
rooms.sort()
count_rooms = []
for i in rooms:
    tmp = data[data['Rooms'] == i]
    count_rooms.append(tmp.shape[0])
width = 1
plot = plt.bar(rooms, count_rooms, width, color='r')
plt.show()
Explanation: Теперь посмотрим на распределение числа комнат
End of explanation
plt.hist(data['Distance'], color='r')
plt.show()
Explanation: Теперь на распределение расстояний до центра города
End of explanation
data[data['Distance'] > 28].shape[0]
tmp = data[data['Distance'] > 28]
tmp[['Distance', 'link']]
Explanation: Снова изучим хвост ( > 28 )
End of explanation
data[data['Distance'] > 27].shape[0]/data.shape[0]
Explanation: Вот здесь уже появляется странная информация. Например, реальный адрес по Одинцовскому району Московской области, но при этом указано м. Парк Победы. Да, от него наверное удобно добираться, если ехать из Москвы. Но это не отменяет того, что на самом деле поездка такая будет ИЗ Москвы и уже в Подмосковье.
Или еще пример - 10 минут на машине от м. Алтуфьево, мкр. Северный. Открываем карту - это Северный мкр. Домодедово. При чем тут Алтуфьево? При чем тут Москва?
В общем квартиры по-настоящему далеко. Их мало. Они разные (есть Рублевское направление, есть Домодедово). Обучиться на них хорошо предсказывать цену на удаленные от центра квартиры мы не сможем. Так что я хочу определить максимальную удаленность границы рассматриваемых округов от центра города и оценить объем данных, не проходящих этот порог.
End of explanation
data = data[data['Distance'] <= 27]
data.index = [x for x in range(len(data.index))]
Explanation: Прикинув по карте, получила 27. Не проходящих порог данных меньше 1%, удаляю их.
End of explanation
plt.hist(data['Distance'], color='r')
plt.show()
Explanation: Снова посмотрим на распределение
End of explanation
data.columns
data_corr = data[['Distance', 'Floor', 'Kitsp', 'Livsp', 'New', 'Nfloors', 'Price',
       'Rooms', 'Totsp', 'Metrokm']]
correlation = data_corr.corr()
f, ax = plt.subplots(figsize=(10, 10))
cmap = sns.diverging_palette(220, 10, as_cmap=True)
sns.heatmap(correlation, cmap=cmap, vmax=.3,
            square=True, xticklabels=correlation.columns.values, 
            yticklabels=correlation.columns.values,
            linewidths=.5, cbar_kws={"shrink": .5}, ax=ax)
Explanation: Стало лучше.
Теперь поищем корреляции в данных, построим немного графиков.
End of explanation
plot = plt.scatter(data['Rooms'], data['Price'], color = 'r')
plt.show()
Explanation: Видна положительная корреляция цены с площадями и числом комнат, а также отрицательная с расстоянием до центра. Выглядит понятно
Посмотрим на зависимость цены от количества комнат
End of explanation
plot = plt.scatter(data['Distance'], data['Price'], color = 'r')
plt.show()
Explanation: Чем больше комнат, тем больше цена, многокомнатных меньше, чем других вариантов
Теперь построим аналогичный график для расстояний от центра города
End of explanation
plot = plt.scatter(data['Totsp'], data['Price'], color = 'r')
plt.show()
Explanation: Чем больше расстояние до центра, тем меньше цена
Теперь посмотрим на зависимость цены от общей площади
End of explanation
prices_distr = []
for i in districts.keys():
    tmp = data[data['Distr'] == districts.get(i)]
    price = tmp['Price'].median()
    prices_distr.append(price)
plot = plt.bar(range(9), prices_distr, color='r')
plt.show()
districts
Explanation: Чем больше площадь, тем больше цена
И напоследок посмотрим на зависимость цены от округа
End of explanation
to_encode = ['Bal', 'Brick', 'Distr']
data[to_encode] = data[to_encode].fillna(value = 'na')
onehots = pd.get_dummies(data[to_encode])
onehots.head()
data = pd.concat([data, onehots], axis=1)
data.columns
data.drop(to_encode, axis = 1, inplace = True)
data.drop('link', axis = 1, inplace = True)
data.shape
Explanation: С большим отрывом лидирует ЦАО
Вспомним, что хотели сделать one hot encoding для некоторых признаков, выбросим после этого их и ссылки
End of explanation
data.to_csv('cian_data_clear_for_modeling.csv', index = False)
Explanation: И сохраним данные. Остальное в следующем ноутбуке
End of explanation
<END_TASK>