<jupyter_start><jupyter_text>Исследование объявлений о продаже квартирВ нашем распоряжении данные сервиса Яндекс.Недвижимость — архив объявлений о продаже квартир в Санкт-Петербурге и соседних населённых пунктов за несколько лет. Нужно научиться _определять рыночную стоимость объектов недвижимости_.🎯 _Задача — установить параметры_. Это позволит построить автоматизированную систему: она отследит аномалии и мошенническую деятельность. По каждой квартире на продажу доступны два вида данных:* первые вписаны пользователем* вторые — получены автоматически на основе картографических данных. Например, расстояние до центра, аэропорта, ближайшего парка и водоёма.  Changelog  04.10.2019- Добавлена ячейка с выводом дублирующихся названий населенных пунктов- Пропуски в названиях населенных пунктов оставлены нетронутыми на этапе предобработки данных- Для `ceiling_height`    - обновлен "ящик с усами" (показаны 25% значений)    - добавлен комментарий по поводу ошибок    - установлено минимальное значение в 2.4 м (допустимое нормами строительства)- При анализе `living_area` используется круговые диаграммы для наглядного представления долей- EDA:    - Более адеватный анализ с учетом ошибок и выбросов (в том числе по высоте потолков)    - Добавлены гистограммы и заметки    - Исправлены выводы по факторам, влияющим на стоимость недвижимости- Исправлены общие выводы  Шаг 1. Откроем файл с данными и изучим общую информацию<jupyter_code>import pandas as pd
import matplotlib.pyplot as plt

realty = pd.read_csv('data.csv')
realty.head()<jupyter_output><empty_output><jupyter_text>❗️ Видим одну колонку, в которой фактические признаки разделены символом табуляции `\t` – прочитаем файл, указав этот разделитель.<jupyter_code>realty = pd.read_csv('data.csv', sep='\t')
realty.head()<jupyter_output><empty_output><jupyter_text>Получили 22 признака: | Название признака    | Описание                                                         ||----------------------|------------------------------------------------------------------|| airports_nearest     | расстояние до ближайшего аэропорта в метрах (м)                  || balcony              | число балконов                                                   || ceiling_height       | высота потолков (м)                                              || cityCenters_nearest  | расстояние до центра города (м)                                  || days_exposition      | сколько дней было размещено объявление (от публикации до снятия) || first_day_exposition | дата публикации                                                  || floor                | этаж                                                             || floors_total         | всего этажей в доме                                              || is_apartment         | апартаменты (булев тип)                                          || kitchen_area         | площадь кухни в квадратных метрах (м²)                           || last_price           | цена на момент снятия с публикации                               || living_area          | жилая площадь в квадратных метрах(м²)                            || locality_name        | название населённого пункта                                      || open_plan            | свободная планировка (булев тип)                                 || parks_around3000     | число парков в радиусе 3 км                                      || parks_nearest        | расстояние до ближайшего парка (м)                               || ponds_around3000     | число водоёмов в радиусе 3 км                                    || ponds_nearest        | расстояние до ближайшего водоёма (м)                             || rooms                | число комнат                                                     || studio               | квартира-студия (булев тип)                                      || total_area           | площадь квартиры в квадратных метрах (м²)                        || total_images         | число фотографий квартиры в объявлении                           | Теперь выведем общую информацию о датафрейме и посмотрим на типы данных.<jupyter_code>realty.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 23699 entries, 0 to 23698
Data columns (total 22 columns):
total_images            23699 non-null int64
last_price              23699 non-null float64
total_area              23699 non-null float64
first_day_exposition    23699 non-null object
rooms                   23699 non-null int64
ceiling_height          14504 non-null float64
floors_total            23613 non-null float64
living_area             21796 non-null float64
floor                   23699 non-null int64
is_apartment            2775 non-null object
studio                  23699 non-null bool
open_plan               23699 non-null bool
kitchen_area            21421 non-null float64
balcony                 12180 non-null float64
locality_name           23650 non-null object
airports_nearest        18157 non-null float64
cityCenters_nearest     18180 non-null float64
parks_around3000        18181 non-null float64
parks_nearest           8079 non-null float64
ponds_around300[...]<jupyter_text>23 699 записей.  Проверим наличие дубликатов<jupyter_code># названия населенных пунктов – вдруг есть разные написания
localities = realty['locality_name'].sort_values().unique()
localities<jupyter_output><empty_output><jupyter_text>⚠️ Помимо `е` и `ё` в слове `поселок` в списке видим населенные пункты, которые изменили свой статус:* `поселок Мурино` и `Мурино`* `деревня Кудрово` и `Кудрово`P.S. Для удобства мы могли бы перенести тип населенного пункта в самостоятельный признак, но пока ограничимся только перечисленными правками на этапе предобработки данных. К тому же, мы не знаем есть ли населенные пункты с одинаковыми названиями. Поэтому такой вариант видится на данный момент оптимальным.<jupyter_code># дубликаты по всем признакам
realty.duplicated().sum()<jupyter_output><empty_output><jupyter_text>...и пропусков<jupyter_code>realty.isna().sum()<jupyter_output><empty_output><jupyter_text>Вывод 1. Явных дубликатов в данных нет. Нужно будет проверить еще раз на этапе заполнения пропусков и после него 💡 2. В названиях населенных пунктов встречается разное написание слова `поселок` – с `е` и `ё`. Приведем слова к единому написанию. 3. Пропусков хоть отбавляй, особенно для признака `апартаменты`.    ☝🏻 Но, для проведения анализа данных и построения выводов нам НЕ понадобятся перечисленые ниже признаки. Пропуски и проблемы с типами в них мы можем проигнорировать.    `is_apartment` (20 924) – с апартаментами все сложно. Само понятие `апартаменты` довольно размытое. Гoвopя ycлoвнo, _aпapтaмeнты — нeжилыe пoмeщeния, в кoтopыx coздaны ycлoвия для пpoживaния_.         Пропусков в количестве парков/водоемов почти в 3 раза меньше, чем пропусков в признаке расстояния до ближайшего парка/водоема. То ли пользователи приукрашивают объеты недвижимости, то ли есть расхождения в понятиях. Например, является ли водоемом озеро в деревне или лес рядом с поселком? Встает вопрос: кому же доверять – значениям на основе картографических данных (актуальные ли они? нет ли ошибки?), или данным, которые вводит продавец? Как говорил Dr.House – "Everybody lies". 4. Для следующих же признаков необходимо преобразование типов:| Признак          | Исходный тип | Необходимый тип | Причина замены                                       ||------------------|--------------|-----------------|------------------------------------------------|| balcony          | float64      | int64           | Число балконов должно быть целым                   || floors_total     | float64      | int64           | Всего этажей в доме не может быть вещественным числом || days_exposition  | float64      | int64           | Количество дней публикации – целое число             | 5. `first_day_exposition` представляет собой дату публикации объявления. Для нее мы бы отбросим время приведя столбец к формату `%Y-%m-%d`.  Шаг 2. Предобработка данных  Изучим и заполним по возможноси пропущенные значения ✔ 1. `floors_total` – всего этажей в доме. Можно предположить, что это одноэтажные дома. Заполним пропуски значением `1`. Даже если наше предположение ошибочно, 86 записей не должны сильно сдвинуть распределение.<jupyter_code>realty['floors_total'] = realty['floors_total'].fillna(1)<jupyter_output><empty_output><jupyter_text>✔️ 2. `balcony` – число балконов. Примерно половина записей содержит пропуски. Вполне вероятно, что у недвижимости просто нет балконов. К слову, лоджия – это уже часть жилого помещения. Заполним пропуски `0`.<jupyter_code>realty['balcony'] = realty['balcony'].fillna(0)<jupyter_output><empty_output><jupyter_text>✔️ 3. `locality_name` – название населенного пункта. Всего 49 пропусков – оставим их для следующего этапа. Хорошо было бы дать фидбек разработчикам портала Недвижимости, чтобы они сделали название населенного пункта обязательным. Продажа объекта _in the middle of nowhere_ – сомнительное занятие. Выше мы уже выделили список названий всех населенных пунктов и сохранили в переменную `localities`.<jupyter_code>len(localities)<jupyter_output><empty_output><jupyter_text>Сделаем необходимые замены для того, чтобы исключить дубликаты.<jupyter_code>def normalize_locality_name(value):
    if str(value) == 'nan':
        return
    
    localities = ['Мурино', 'Кудрово']
    words = value.split(' ')
    
    if words[0] == 'посёлок':
        words[0] = 'поселок'
    
    for locality in localities:
        if locality in words:
            return locality
        
    return ' '.join(words)
realty['locality_name'] = realty['locality_name'].apply(normalize_locality_name)
realty['locality_name'].value_counts()<jupyter_output><empty_output><jupyter_text>📝 _Таким образом, в нашем датасете участвуют `329` населенных пунктов. Большая часть объявлений приходится на Петербург и другие города._ ✔️ 4. `ceiling_height` (9 195) – высота потолков (м). Пользователи явно не любят вставать на стремянку и делать замеры. В среднем, этот параметр должен колебаться в пределах _2.4 - 3.2 м_. Минимальная высота потолка определяется строительными нормами и правилами как 2.5 м. На этапе EDA детальнее изучим данные и при необходимости почистим их. ✔️ 5. `living_area` (1 903) – нас больше интересует общая площадь квартиры в квадратных метрах (м²).🚫Заполнить пропуски медианой – не совсем правильное решение, т.к. медиана может быть больше общей площади квартиры.Проверим не являются ли пропуски характерными для апартаментов или студий, где жилая площадь не так очевидна или пользователи просто не добавили данные.<jupyter_code>missing_living_area = realty[realty['living_area'].isnull()]
missing_living_area['is_apartment'].value_counts().plot(kind='pie')
plt.show()
missing_living_area['studio'].value_counts().plot(kind='pie')
plt.show()
# Непохоже...заполняем медианой
realty.loc[realty['living_area'].isnull(), 'living_area'] = realty['living_area'].median()<jupyter_output><empty_output><jupyter_text>✔️ 6. `days_exposition` (3 181) – сколько дней было размещено объявление (от публикации до снятия). На эти пропуски мы могли бы указать разработчикам портала недвижимости, ведь дата публикации есть у всех записей, и они явно могут определить, когда объявление было снято с публикации. Для восстановления пропусков у нас нет подходящего признака, поэтому заполним их опять медианой. Проверим распределение на этапе EDA.<jupyter_code>realty.loc[realty['days_exposition'].isnull(), 'days_exposition'] = realty['days_exposition'].median()<jupyter_output><empty_output><jupyter_text>✔️ 7. `first_day_exposition` приведем к к формату `%Y-%m-%d`:<jupyter_code>realty['first_day_exposition'] = pd.to_datetime(realty['first_day_exposition'], format='%Y-%m-%d')<jupyter_output><empty_output><jupyter_text>✔️ 8. Часть пропусков признаков *расстояние до x* `airports_nearest` (5 542), `cityCenters_nearest` (5 519) попробуем заполнить медианой по срезу данных по населенному пункту. Но как мы видим, для некоторых записей восстановить пропуски мы не сможем. В этих случаях, скорее всего, пропуски появляются в населенных пунктах, которые не являются городами или прилично удалены от административных центров. Либо же нет картографических данных для рассчета расстояний.<jupyter_code>median_data = realty.pivot_table(
    index='locality_name',
    values=['airports_nearest', 'cityCenters_nearest'],
    aggfunc='median'
)
median_data
median_data_localities = list(median_data.index)
def fill_missing_data(row, col):
    locality = row['locality_name']
    
    if locality in median_data_localities:
        return median_data.loc[locality][col]
    
    return row[col]
columns = ['airports_nearest', 'cityCenters_nearest']

for column in columns:
    realty.loc[realty[column].isnull(), column] = realty.apply(fill_missing_data, axis=1, args=(column,))
# Проверим данные по скольким населенные пунктам не удалось заполнить
len(realty[realty['cityCenters_nearest'].isnull()]['locality_name'].unique())<jupyter_output><empty_output><jupyter_text>---После заполнения пропусков проверим еще раз дубликаты.<jupyter_code>realty.duplicated().sum()<jupyter_output><empty_output><jupyter_text>Приведение данных к нужным типам<jupyter_code># float63 -> int64
for column in ['balcony', 'floors_total', 'days_exposition']:
    realty[column] = realty[column].astype('int')<jupyter_output><empty_output><jupyter_text>Шаг 3. Посчитаем и добавим в таблицу  1. Цену квадратного метра<jupyter_code>realty['price_square_meter'] = realty['last_price'] / realty['total_area']<jupyter_output><empty_output><jupyter_text>2. День недели, месяц и год публикации объявления<jupyter_code>realty['day_exposition'] = realty['first_day_exposition'].dt.dayofweek
realty['month_exposition'] = realty['first_day_exposition'].dt.month
realty['year_exposition'] = realty['first_day_exposition'].dt.year
realty.head()<jupyter_output><empty_output><jupyter_text>3. Этаж квартиры<jupyter_code>def categorize_floor(row):
    """Категоризация этажей"""

    if row['floor'] == 1:
        return 'первый'
    elif row['floor'] == row['floors_total']:
        return 'последний'
    else:
        return 'другой'
    
    
realty.loc[:, 'floor'] = realty.apply(categorize_floor, axis=1)
realty['floor'].value_counts()<jupyter_output><empty_output><jupyter_text>4. Соотношение жилой и общей площади<jupyter_code># Помним, что мы не заполняли пропуски у признака жилой площади, поэтому данный признак будет тоже содержать пропуски.
realty['living_total_areas_ration'] = realty['living_area'] / realty['total_area']<jupyter_output><empty_output><jupyter_text>5. Соотношение площади кухни к общей<jupyter_code>realty['kitchen_total_areas_ration'] = realty['kitchen_area'] / realty['total_area']
realty.head()<jupyter_output><empty_output><jupyter_text>Шаг 4. Исследовательский анализ данных 💡 Следим за выбросами и ищем странности в распределении.  Площадь<jupyter_code>realty['total_area'].describe()
plt.ylim(0, 200)
realty.boxplot('total_area')
plt.show()<jupyter_output><empty_output><jupyter_text>* Большая часть значений лежит в диапазоне до 70 кв.м., что выглядит правдаподобно.* Первый квартиль находится на уровне 40 кв.м.* Нижний "ус" упирается в 12, минимальное значение. Да, на самом деле есть коморки 12 кв.м.* Верхний ус заканчивается около 115 кв.м.* Все, что лежит за ориентировочной границей нормального размаха, считаем выбросом.📝 _Таким образом, нам нужен будет следующией диапазон значений: 12 – 115 кв.м._<jupyter_code># Грязные данные
realty['total_area'].hist(bins=100)
plt.show()
# Данные без выбросов и ошибок
realty.query('total_area >= 12 and total_area <= 115')['total_area'].hist()
plt.show()<jupyter_output><empty_output><jupyter_text>Цена Мы ожидаем, что цена будет сильно отличаться в зависимости от населенного пункта. В Санкт-Петербурге в среднем должно быть самое дорогое жилье, а в деревнях и поселках – намного дешевле.<jupyter_code>realty['last_price'].describe()
plt.ylim(-1e+06, 1.4e+07)
realty.boxplot('last_price')
plt.show()<jupyter_output><empty_output><jupyter_text>* Большая часть значений лежит в диапазоне до 7 млн., что выглядит правдаподобно.* Первый квартиль на уровне 3.4 млн.* Нижний "ус" упирается в `0` ⚠️ Минимум равен `12 190`, в то время как средняя стоимость недвижимости – 6.5 млн. 😲 Это выглядит подозрительным. Взглянем на подобные записи.<jupyter_code>cheap_realty = realty.query('last_price < 4.5e+5')
cheap_realty.sort_values('last_price').head()[['locality_name', 'last_price', 'total_area']]<jupyter_output><empty_output><jupyter_text>Санкт-Петербург! 109 кв.м.! 2 комнаты! всего 12 190 руб. Надо брать! 🤑Видим ошибку в данных!📝 _Отменим нижнюю границу для чистых данных в 430 000 тыс._Смоимость в полмиллиона для деревней выглядит правдивой. Согласно диаграмме размаха, верхний ус заканчивается около `12 000 000`. Данные выше этого значения считаются выбросами. С другой стороны, есть много ну очень дорогой недвижимости, запредельная стоимость которой, является правдоподобной.<jupyter_code># Грязные данные
realty['last_price'].hist(bins=100)
plt.show()
# Данные без выбросов и ошибок
realty.query('last_price >= 4.3e+5 and last_price <= 1.2e+7')['last_price'].hist()
plt.show()<jupyter_output><empty_output><jupyter_text>Число комнат<jupyter_code>realty['rooms'].describe()
plt.ylim(-1, 10)
realty.boxplot('rooms')
plt.show()<jupyter_output><empty_output><jupyter_text>* Большая часть значений лежит в диапазоне до 3х комнат, что выглядит правдаподобно.* Первый квартиль находится на уровне 1 комнаты.* Нижний "ус" упирается в 0, минимальное значение. ⚠️Считаем за ошибку в данных.* Верхний ус заканчивается около 6.* Выбросы лежат за ориентировочной границей нормального распределения.<jupyter_code># Грязные данные
realty['rooms'].hist(bins=50)
plt.show()
# Данные без выбросов и ошибок
realty.query('rooms >= 1 and rooms <= 6')['rooms'].hist()
plt.show()<jupyter_output><empty_output><jupyter_text>📝 _В ходу, как обычно, однушки, двушки и трешки._  Высота потолков<jupyter_code>realty['ceiling_height'].describe()
plt.ylim(1, 3.5)
realty.boxplot('ceiling_height')
plt.show()<jupyter_output><empty_output><jupyter_text>* Большая часть значений лежит в диапазоне до 2.8 м комнат, что выглядит правдаподобно.* Первый квартиль находится на уровне 2.5 м.* Нижний "ус" упирается в 2.2 м ⚠️* Верхний ус заканчивается около 3.2 м.В среднем, параметр высоты потолков должен колебаться в пределах 2.4 - 3.2 м. Минимальная высота потолка определяется строительными нормами и правилами обычно как 2.5 м.<jupyter_code># Грязные данные
realty['ceiling_height'].hist(bins=50)
plt.show()
# Данные без выбросов и ошибок
realty.query('ceiling_height >= 2.4 and ceiling_height <= 3.2')['ceiling_height'].hist(bins=50)
plt.show()<jupyter_output><empty_output><jupyter_text>Время продажи<jupyter_code>realty['days_exposition'].describe()
plt.ylim(-10, 500)
realty.boxplot(column='days_exposition')
plt.show()<jupyter_output><empty_output><jupyter_text>* Большая часть значений лежит в диапазоне до 200 дней, что выглядит правдоподобно. Продать квартиру – дело небыстрое.* Первый квартиль лежит на уровне 45 дней.* Нижний ус упирается в 1, минимальное значение. ⚠️* Верхний заканчивается в около 430 дней. 📝 Таким образом:* продажа обычно занимает около 100 дней;* если объект продан в течение 50 дней – это необычайно быстро; также есть вероятность, что собственник передумал продавать квартиру и снял объявление с публикации. Стоит ли отсекать такие значения – вопрос, наверное, к product owner. Будем рассматривать временной промежуток от недели.* если в диапазоне от 200 дней – необычайно долго.<jupyter_code># Грязные данные
realty['days_exposition'].hist(bins=100)
plt.show()
# Данные без выбросов и ошибок
realty.query('days_exposition >= 7 and days_exposition <= 430')['days_exposition'].hist(bins=100)
plt.show()<jupyter_output><empty_output><jupyter_text>Изучим факторы, влияющие на стоимость недвижимости Факторы будем рассматривать в контексте данных, прошедших очистку на предыдущих этапах. Для срезов у нас будут следующие условия:<jupyter_code>total_area_query = 'total_area >= 12 and total_area <= 115'
last_price_query = 'last_price >= 4.3e+5 and last_price <= 1.2e+7'
rooms_query = 'rooms >= 1 and rooms <= 6'
ceiling_height_query = 'ceiling_height >= 2.4 and ceiling_height <= 3.2'
days_exposition_query = 'days_exposition >= 7 and days_exposition <= 430'<jupyter_output><empty_output><jupyter_text>Зависимость стоимости недвижимости от цены квадратного метра<jupyter_code>clean_data_query = f'{total_area_query} and {last_price_query}'
clean_data = realty.query(clean_data_query)

clean_data.plot(x='price_square_meter', y='last_price', kind='scatter')
plt.show()
clean_data['price_square_meter'].corr(clean_data['last_price'])<jupyter_output><empty_output><jupyter_text>📝 По графикам и коэффициенту корреляции видим, что взаимосвязь между изменением цены за квадратный метр и стоимостью недвижимости (ее ростом) есть, и она не такая уж и слабая.  Зависимость стоимости недвижимости от числа комнат<jupyter_code>clean_data_query = f'{rooms_query} and {last_price_query}'
clean_data = realty.query(clean_data_query)

clean_data.plot(x='rooms', y='last_price', kind='scatter')
plt.show()
clean_data['rooms'].corr(clean_data['last_price'])<jupyter_output><empty_output><jupyter_text>📝 Корреляция и график иллюстрируют слабую завимисоть между количеством комнат и стоимостью недвижимости.  Зависимость стоимости недвижимости от этажа<jupyter_code>realty[['floor', 'last_price']].query(last_price_query).pivot_table(index='floor', values='last_price')<jupyter_output><empty_output><jupyter_text>📝 Первый этаж – самый дешевый, на последнем этаже любям селиться не все, а этажи между, как правило, самые популярные.  Зависимость стоимости недвижимости от расстояние до центра города (м)<jupyter_code>clean_data = realty.query(last_price_query)
realty.plot(x='cityCenters_nearest', y='last_price', kind='scatter')
plt.show()
clean_data['cityCenters_nearest'].corr(clean_data['last_price'])<jupyter_output><empty_output><jupyter_text>📝 По коэффициенту Пирсона видим, что так же есть слабая взаимосвязь между расстоянием от центра и ценой – к цетру дороже. Большинство объектов недвижимости так или иначе недалеко от центра. Основная часть дорогих объектов ожидаемо ближе к центру.  Зависимость стоимости недвижимости от даты публикации объявления<jupyter_code>clean_data = realty.query(f'{days_exposition_query} and {last_price_query}')
clean_data[['day_exposition', 'month_exposition', 'year_exposition', 'last_price']].corr()<jupyter_output><empty_output><jupyter_text>📝 Связи между днями недели, месяцами, годами публикации и стоимостью недвижимости нет. Коэффициент Пирсона практически равен `0`. Из трех признаков только год чуть-чуть выделяется. К слову, из таблицы корреляции мы видим связь между месяцем и годом.  10 населённых пунктов с наибольшим числом объявлений<jupyter_code>top_localities = realty['locality_name'].value_counts().head(10)
top_localities
top_locality_names = list(top_localities.index)
(
    realty.query(f'locality_name in @top_locality_names and {last_price_query}')
    .pivot_table(index='locality_name', values='price_square_meter')
    .sort_values(by='price_square_meter', ascending=False)
)<jupyter_output><empty_output><jupyter_text>📝 В Санкт-Петербурге квадратный метр самый дорогой – столица, хоть и северная. В Выборге – самый дешевый.  Недвижимость в Питере<jupyter_code># Для начала выберем всю недвижимость города
saint_petersburg = pd.DataFrame(realty.loc[realty['locality_name'] == "Санкт-Петербург"])
saint_petersburg.reset_index()
len(saint_petersburg)<jupyter_output><empty_output><jupyter_text>🎯 _Выясним, какая область входит в центр._<jupyter_code># Создадим столбец с расстоянием до центра в километрах округляя до целых значений
km = 1_000
saint_petersburg['cityCenters_nearest_km'] = (saint_petersburg['cityCenters_nearest'] / km).astype('int')<jupyter_output><empty_output><jupyter_text>Посчитаем среднюю цену для каждого километра удаленности учитывания:<jupyter_code>spb_prices = saint_petersburg.query(last_price_query)
spb_prices.pivot_table(index='cityCenters_nearest_km', values='last_price')<jupyter_output><empty_output><jupyter_text>Построим график, который показывает как цена зависит от удалённости от центра:<jupyter_code>(
    pd.DataFrame(spb_prices.pivot_table(index='cityCenters_nearest_km', values='last_price').to_records())
    .plot(x='cityCenters_nearest_km', y='last_price', kind='scatter', grid=True)
)
plt.show()<jupyter_output><empty_output><jupyter_text>📝 Определим границу, где график сильно меняется — это и будет центральная зона. В нашем случае это 7 км. ----⭐️ Выделим сегмент квартир в центре.<jupyter_code>apartments_in_center = pd.DataFrame(spb_prices.query('cityCenters_nearest_km <= 7'))
len(apartments_in_center)<jupyter_output><empty_output><jupyter_text>---**Проанализируем эти `2 503` объектов недвижимости. Для начала изучим параметры.**<jupyter_code># площадь
apartments_in_center.query(total_area_query)['total_area'].describe()<jupyter_output><empty_output><jupyter_text>_Средняя площадь 63 кв.м._<jupyter_code># цена
apartments_in_center['last_price'].describe()<jupyter_output><empty_output><jupyter_text>_Средняя цена 7.5 млн. руб._<jupyter_code># число комнат
apartments_in_center.query(rooms_query)['rooms'].describe()<jupyter_output><empty_output><jupyter_text>_Больше объявлений по двушкам и трешкам._<jupyter_code># высота потолков
apartments_in_center.query(ceiling_height_query)['ceiling_height'].describe()<jupyter_output><empty_output><jupyter_text>_Средняя высота потолков ~2.9 м. Нормальные потолки, в общем._ ---**Выделим факторы, влияющие на стоимость квартиры.**<jupyter_code># Ранее мы уже сделали срез по квартирам в центре Питера с учетом границы нормального размаха цены.
# Добавим остальные критерии, влияющие на чистоту данных, и сделаем окончательный срез на их основе.
clean_data_query = f'{total_area_query} and {rooms_query} and {ceiling_height_query} and {days_exposition_query}'
clean_data = apartments_in_center.query(clean_data_query)
features = [
    'last_price',
    'cityCenters_nearest_km',
    'total_area',
    'rooms',
    'ceiling_height',
    'day_exposition',
    'month_exposition',
    'year_exposition'
]

clean_data[features].corr().sort_values('last_price', ascending=False)
# этаж
clean_data.pivot_table(index='floor', values='last_price').sort_values(by='last_price')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09777307270596242, 'fraction_numerical': 0.016900449922492344, 'mean_word_length': 4.595515125872646, 'pattern_counts': {'":': 0, '<': 209, '<?xml version=': 0, '>': 208, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 32, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '49', 'dir': 'jupyter-structured-clean-dedup', 'id': '516915', 'license': 'MIT', 'n_tokens_mistral': 10415, 'n_tokens_neox': 11465, 'n_words': 2311, 'path': 'sprint3/notebook.ipynb', 'repo_name': 'akrisanov/yandex-praktikum-data-science', 'size': '26449'}