В вашем распоряжении данные сервиса Яндекс.Недвижимость — архив объявлений о продаже квартир в Санкт-Петербурге и соседних населённых пунктов за несколько лет. Нужно научиться определять рыночную стоимость объектов недвижимости. Ваша задача — установить параметры. Это позволит построить автоматизированную систему: она отследит аномалии и мошенническую деятельность.
По каждой квартире на продажу доступны два вида данных. Первые вписаны пользователем, вторые — получены автоматически на основе картографических данных. Например, расстояние до центра, аэропорта, ближайшего парка и водоёма.
import pandas as pd
import numpy as np
import math
import seaborn as sns
import matplotlib.pyplot as plt
import plotly.express as px
import warnings
warnings.filterwarnings('ignore')
data = pd.read_csv('real_estate_data.csv', sep='\t')
display(data.head(), data.tail())
total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | kitchen_area | balcony | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20 | 13000000.0 | 108.0 | 2019-03-07T00:00:00 | 3 | 2.70 | 16.0 | 51.0 | 8 | NaN | ... | 25.0 | NaN | Санкт-Петербург | 18863.0 | 16028.0 | 1.0 | 482.0 | 2.0 | 755.0 | NaN |
1 | 7 | 3350000.0 | 40.4 | 2018-12-04T00:00:00 | 1 | NaN | 11.0 | 18.6 | 1 | NaN | ... | 11.0 | 2.0 | посёлок Шушары | 12817.0 | 18603.0 | 0.0 | NaN | 0.0 | NaN | 81.0 |
2 | 10 | 5196000.0 | 56.0 | 2015-08-20T00:00:00 | 2 | NaN | 5.0 | 34.3 | 4 | NaN | ... | 8.3 | 0.0 | Санкт-Петербург | 21741.0 | 13933.0 | 1.0 | 90.0 | 2.0 | 574.0 | 558.0 |
3 | 0 | 64900000.0 | 159.0 | 2015-07-24T00:00:00 | 3 | NaN | 14.0 | NaN | 9 | NaN | ... | NaN | 0.0 | Санкт-Петербург | 28098.0 | 6800.0 | 2.0 | 84.0 | 3.0 | 234.0 | 424.0 |
4 | 2 | 10000000.0 | 100.0 | 2018-06-19T00:00:00 | 2 | 3.03 | 14.0 | 32.0 | 13 | NaN | ... | 41.0 | NaN | Санкт-Петербург | 31856.0 | 8098.0 | 2.0 | 112.0 | 1.0 | 48.0 | 121.0 |
5 rows × 22 columns
total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | kitchen_area | balcony | locality_name | airports_nearest | cityCenters_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
23694 | 9 | 9700000.0 | 133.81 | 2017-03-21T00:00:00 | 3 | 3.7 | 5.0 | 73.3 | 3 | NaN | ... | 13.83 | NaN | Санкт-Петербург | 24665.0 | 4232.0 | 1.0 | 796.0 | 3.0 | 381.0 | NaN |
23695 | 14 | 3100000.0 | 59.00 | 2018-01-15T00:00:00 | 3 | NaN | 5.0 | 38.0 | 4 | NaN | ... | 8.50 | NaN | Тосно | NaN | NaN | NaN | NaN | NaN | NaN | 45.0 |
23696 | 18 | 2500000.0 | 56.70 | 2018-02-11T00:00:00 | 2 | NaN | 3.0 | 29.7 | 1 | NaN | ... | NaN | NaN | село Рождествено | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
23697 | 13 | 11475000.0 | 76.75 | 2017-03-28T00:00:00 | 2 | 3.0 | 17.0 | NaN | 12 | NaN | ... | 23.30 | 2.0 | Санкт-Петербург | 39140.0 | 10364.0 | 2.0 | 173.0 | 3.0 | 196.0 | 602.0 |
23698 | 4 | 1350000.0 | 32.30 | 2017-07-21T00:00:00 | 1 | 2.5 | 5.0 | 12.3 | 1 | NaN | ... | 9.00 | NaN | поселок Новый Учхоз | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
5 rows × 22 columns
# Проведем разведочный анализ данных
data.shape
(23699, 22)
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 23699 entries, 0 to 23698 Data columns (total 22 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23699 non-null int64 1 last_price 23699 non-null float64 2 total_area 23699 non-null float64 3 first_day_exposition 23699 non-null object 4 rooms 23699 non-null int64 5 ceiling_height 14504 non-null float64 6 floors_total 23613 non-null float64 7 living_area 21796 non-null float64 8 floor 23699 non-null int64 9 is_apartment 2775 non-null object 10 studio 23699 non-null bool 11 open_plan 23699 non-null bool 12 kitchen_area 21421 non-null float64 13 balcony 12180 non-null float64 14 locality_name 23650 non-null object 15 airports_nearest 18157 non-null float64 16 cityCenters_nearest 18180 non-null float64 17 parks_around3000 18181 non-null float64 18 parks_nearest 8079 non-null float64 19 ponds_around3000 18181 non-null float64 20 ponds_nearest 9110 non-null float64 21 days_exposition 20518 non-null float64 dtypes: bool(2), float64(14), int64(3), object(3) memory usage: 3.7+ MB
# Количество пропусков в датасете
data.isna().sum()
total_images 0 last_price 0 total_area 0 first_day_exposition 0 rooms 0 ceiling_height 9195 floors_total 86 living_area 1903 floor 0 is_apartment 20924 studio 0 open_plan 0 kitchen_area 2278 balcony 11519 locality_name 49 airports_nearest 5542 cityCenters_nearest 5519 parks_around3000 5518 parks_nearest 15620 ponds_around3000 5518 ponds_nearest 14589 days_exposition 3181 dtype: int64
# Количество явных дубликатов
data.duplicated().sum()
0
data.hist(figsize=(15, 20));
Подведем итоги разведовочного анализа
floors_total
, balcony
, parks_around3000
, ponds_around3000
и days_exposition
тип данных должны быть целочисленными, is_apartment
- булевым, а first_day_exposition
- тип даты и времениcityCenters_nearest
в соответствии со стилем snake_caseceiling_height
с максимальной высотой потолка в 100 метров и колонка room
с количеством ноль комнатДанные представлены в виде таблицы, включающей в себя 23 698 строк и 22 столбца. При этом 3 столбца - с целочисленными данными (тип < int >), 14 столбцов - с числовыми данными типа < float >, 3 столбца типа < object > и 2 столбца с булевым типом (тип < bool >). Разберём, какие в таблице столбцы, и какую информацию они содержат:
total_images
— число фотографий квартиры в объявлении;
last_price
— цена на момент снятия с публикации;
total_area
— площадь квартиры в квадратных метрах (м²);
first_day_exposition
— дата публикации;
rooms
— число комнат;
ceiling_height
— высота потолков (м);
floors_total
— всего этажей в доме;
living_area
— жилая площадь в квадратных метрах(м²);
floor
— этаж;
is_apartment
— апартаменты;
studio
— квартира-студия;
open_plan
— свободная планировка;
kitchen_area
— площадь кухни в квадратных метрах (м²);
balcony
— число балконов;
locality_name
— название населённого пункта;
airports_nearest
— расстояние до ближайшего аэропорта в метрах (м);
cityCenters_nearest
— расстояние до центра города (м);
parks_around3000
— число парков в радиусе 3 км;
parks_nearest
— расстояние до ближайшего парка (м);
ponds_around3000
— число водоёмов в радиусе 3 км;
ponds_nearest
— расстояние до ближайшего водоёма (м);
days_exposition
— сколько дней было размещено объявление (от публикации до снятия).
Настало время заполнить пропущенные пропуски
**ceiling_height
** - высота потолков
# Начнем по-порядку
# Колонку ceiling_height предлагаю запонить медианным значением
print('Медианная высота потолков:',data.ceiling_height.median())
data.ceiling_height = data.ceiling_height.fillna(2.65)
Медианная высота потолков: 2.65
data.ceiling_height.sort_values(ascending=False).head(60)
22869 100.00 3148 32.00 22336 32.00 21377 27.50 4876 27.00 17857 27.00 5246 27.00 20478 27.00 22938 27.00 5807 27.00 21824 27.00 10773 27.00 5669 26.00 18545 25.00 4643 25.00 9379 25.00 11285 25.00 14382 25.00 355 25.00 6246 25.00 5076 24.00 20507 22.60 17496 20.00 15061 14.00 22309 10.30 5863 8.30 3474 8.00 15743 8.00 17442 8.00 20264 6.00 21227 5.80 1388 5.60 7578 5.50 12628 5.30 1026 5.30 464 5.20 1053 5.00 21923 4.90 2802 4.80 1300 4.70 19142 4.70 12401 4.65 2823 4.50 9783 4.50 10159 4.50 6802 4.50 13224 4.50 14519 4.50 3067 4.50 7521 4.45 8018 4.45 6728 4.40 11651 4.40 7274 4.40 4201 4.37 10754 4.30 17078 4.25 6901 4.20 11128 4.20 2843 4.20 Name: ceiling_height, dtype: float64
data.loc[(data.ceiling_height == 20), 'ceiling_height'] = 2.0
data.loc[(data.ceiling_height == 22.6), 'ceiling_height'] = 2.26
data.loc[(data.ceiling_height == 24), 'ceiling_height'] = 2.4
data.loc[(data.ceiling_height == 25), 'ceiling_height'] = 2.5
data.loc[(data.ceiling_height == 26), 'ceiling_height'] = 2.6
data.loc[(data.ceiling_height == 27), 'ceiling_height'] = 2.7
data.loc[(data.ceiling_height == 27.5), 'ceiling_height'] = 2.75
data.loc[(data.ceiling_height == 32), 'ceiling_height'] = 3.2
# Автомотизировать данный процесс и удалить выбивающиеся значения к сожалению у меня не получилось,
# рассчитываю на вашу подсказку
**floors_total
** - всего этажей в доме
# Строим сводную таблицу с медианным значением
floor_median = data.pivot_table(index='floor', values='floors_total', aggfunc='median').reset_index()
# Строим цикл
for index in range(data.floor.min(), (data.floor.max()+1)):
data.loc[data['floor'] == index, 'floors_total'] = \
(data.loc[data['floor'] == index, 'floors_total'] \
.fillna(floor_median[floor_median['floor'] == index]['floors_total'].sum()))
print('Пропущенных значений:', data.floors_total.isna().sum())
# Меняем тип на int
data.floors_total = data.floors_total.astype('int')
Пропущенных значений: 0
**living_area
** - жилая площадь
"""
При определении жилой площади в расчёт берутся размеры только тех комнат, которые пригодны для проживания
(спальня, детская, гостиная). К жилой площади не относятся кухни, коридоры, санузлы, кладовые, встроенные шкафы,
балконы и лоджии
"""
# Прокоррелируем)) Найдём через корреляцию те столбцы, при помощи которых мы можем произвести заполнение.
сorr = data.corr()
print(сorr.sort_values(by='living_area', ascending=False)['living_area'].reset_index().head())
# Посчитаем усредненное значение жилой площади одной комнаты
df = round(data.pivot_table(index='rooms', values='living_area', aggfunc='mean')).reset_index()
df
index living_area 0 living_area 1.000000 1 total_area 0.939537 2 rooms 0.845977 3 last_price 0.566492 4 kitchen_area 0.428674
rooms | living_area | |
---|---|---|
0 | 0 | 19.0 |
1 | 1 | 18.0 |
2 | 2 | 32.0 |
3 | 3 | 47.0 |
4 | 4 | 67.0 |
5 | 5 | 100.0 |
6 | 6 | 131.0 |
7 | 7 | 164.0 |
8 | 8 | 169.0 |
9 | 9 | 190.0 |
10 | 10 | 166.0 |
11 | 11 | 134.0 |
12 | 12 | 410.0 |
13 | 14 | 195.0 |
14 | 15 | 409.0 |
15 | 16 | 180.0 |
16 | 19 | 264.0 |
round(df.living_area.sum() / df.rooms.sum())
19
# Построим функцию
mean = 19
def replace_living(row):
count = row['rooms']
return count * mean
data['living_area'] = data.apply(replace_living, axis=1)
**is_apartment
** - аппартаменты
# Посмотрим соотношение аппартаментов и не аппартаментов
data.groupby('is_apartment', as_index=False).agg({'rooms':'count'})
is_apartment | rooms | |
---|---|---|
0 | False | 2725 |
1 | True | 50 |
"""Замена осуществляется на False, ввиду того, что пропущенные значения скорей всего означают,
что аппартаменты не входят в предложение по продаже кваритр т.к. при отстутсвии апартаментов человек
не заполнял этот столбец"""
data.is_apartment = data.is_apartment.fillna(False)
# Заменяем тип данных на "bool"
data.is_apartment = data.is_apartment.astype(bool)
**kitchen_area
** - площадь кухни
# Согласно строительным нормам кухня не может быть меньше 5м², предлагаю удалить все варианты ниже данного значения
data.drop(index=data.query('kitchen_area < 5').index,inplace=True)
data.kitchen_area = data.kitchen_area.fillna(0)
**balcony
** - балкон
# Заполним пропущенные данные в колонке наличия балконов, где 0 - нет балкона. Также изменим тип данных на int
data.balcony = data.balcony.fillna(0).astype('int')
**locality_name
** - название населенного пункта
# Исследуем столбец locality_name на предмет уникальных значений
print('Количество уникальных значений:', data['locality_name'].nunique())
data['locality_name'].unique()
Количество уникальных значений: 363
array(['Санкт-Петербург', 'посёлок Шушары', 'городской посёлок Янино-1', 'посёлок Парголово', 'посёлок Мурино', 'Ломоносов', 'Сертолово', 'Петергоф', 'Пушкин', 'деревня Кудрово', 'Коммунар', 'Колпино', 'поселок городского типа Красный Бор', 'Гатчина', 'поселок Мурино', 'деревня Фёдоровское', 'Выборг', 'Кронштадт', 'Кировск', 'деревня Новое Девяткино', 'посёлок Металлострой', 'посёлок городского типа Лебяжье', 'посёлок городского типа Сиверский', 'поселок Молодцово', 'поселок городского типа Кузьмоловский', 'садовое товарищество Новая Ропша', 'Павловск', 'деревня Пикколово', 'Всеволожск', 'Волхов', 'Кингисепп', 'Приозерск', 'Сестрорецк', 'деревня Куттузи', 'посёлок Аннино', 'поселок городского типа Ефимовский', 'посёлок Плодовое', 'деревня Заклинье', 'поселок Торковичи', 'поселок Первомайское', 'Красное Село', 'посёлок Понтонный', 'Сясьстрой', 'деревня Старая', 'деревня Лесколово', 'посёлок Новый Свет', 'Сланцы', 'село Путилово', 'Ивангород', 'Мурино', 'Шлиссельбург', 'Никольское', 'Зеленогорск', 'Сосновый Бор', 'поселок Новый Свет', 'деревня Оржицы', 'деревня Кальтино', 'Кудрово', 'поселок Романовка', 'посёлок Бугры', 'поселок Бугры', 'поселок городского типа Рощино', 'Луга', 'Волосово', 'Отрадное', 'село Павлово', 'поселок Оредеж', 'село Копорье', 'посёлок городского типа Красный Бор', 'посёлок Молодёжное', 'Тихвин', 'посёлок Победа', 'деревня Нурма', 'поселок городского типа Синявино', 'Тосно', 'посёлок городского типа Кузьмоловский', 'посёлок Стрельна', 'Бокситогорск', 'посёлок Александровская', 'деревня Лопухинка', 'Пикалёво', 'поселок Терволово', 'поселок городского типа Советский', 'Кириши', 'Подпорожье', 'посёлок Петровское', 'посёлок городского типа Токсово', 'поселок Сельцо', 'посёлок городского типа Вырица', 'деревня Кипень', 'деревня Келози', 'деревня Вартемяги', 'посёлок Тельмана', 'поселок Севастьяново', 'городской поселок Большая Ижора', nan, 'городской посёлок Павлово', 'деревня Агалатово', 'посёлок Новогорелово', 'городской посёлок Лесогорский', 'деревня Лаголово', 'поселок Цвелодубово', 'поселок городского типа Рахья', 'поселок городского типа Вырица', 'деревня Белогорка', 'поселок Заводской', 'городской посёлок Новоселье', 'деревня Большие Колпаны', 'деревня Горбунки', 'деревня Батово', 'деревня Заневка', 'деревня Иссад', 'Приморск', 'городской посёлок Фёдоровское', 'деревня Мистолово', 'Новая Ладога', 'поселок Зимитицы', 'поселок Барышево', 'деревня Разметелево', 'поселок городского типа имени Свердлова', 'деревня Пеники', 'поселок Рябово', 'деревня Пудомяги', 'поселок станции Корнево', 'деревня Низино', 'деревня Бегуницы', 'посёлок Поляны', 'городской посёлок Мга', 'поселок Елизаветино', 'посёлок городского типа Кузнечное', 'деревня Колтуши', 'поселок Запорожское', 'посёлок городского типа Рощино', 'деревня Гостилицы', 'деревня Малое Карлино', 'посёлок Мичуринское', 'посёлок городского типа имени Морозова', 'посёлок Сосново', 'деревня Аро', 'поселок Ильичёво', 'посёлок городского типа Тайцы', 'деревня Малое Верево', 'деревня Извара', 'поселок станции Вещево', 'село Паша', 'деревня Калитино', 'посёлок городского типа Ульяновка', 'деревня Чудской Бор', 'поселок городского типа Дубровка', 'деревня Мины', 'поселок Войсковицы', 'посёлок городского типа имени Свердлова', 'деревня Коркино', 'посёлок Ропша', 'поселок городского типа Приладожский', 'посёлок Щеглово', 'посёлок Гаврилово', 'Лодейное Поле', 'деревня Рабитицы', 'поселок городского типа Никольский', 'деревня Кузьмолово', 'деревня Малые Колпаны', 'поселок Тельмана', 'посёлок Петро-Славянка', 'городской посёлок Назия', 'посёлок Репино', 'посёлок Ильичёво', 'поселок Углово', 'поселок Старая Малукса', 'садовое товарищество Рахья', 'поселок Аннино', 'поселок Победа', 'деревня Меньково', 'деревня Старые Бегуницы', 'посёлок Сапёрный', 'поселок Семрино', 'поселок Гаврилово', 'поселок Глажево', 'поселок Кобринское', 'деревня Гарболово', 'деревня Юкки', 'поселок станции Приветнинское', 'деревня Мануйлово', 'деревня Пчева', 'поселок Поляны', 'поселок Цвылёво', 'поселок Мельниково', 'посёлок Пудость', 'посёлок Усть-Луга', 'Светогорск', 'Любань', 'поселок Селезнёво', 'поселок городского типа Рябово', 'Каменногорск', 'деревня Кривко', 'поселок Глебычево', 'деревня Парицы', 'поселок Жилпосёлок', 'посёлок городского типа Мга', 'городской поселок Янино-1', 'посёлок Войскорово', 'село Никольское', 'посёлок Терволово', 'поселок Стеклянный', 'посёлок городского типа Важины', 'посёлок Мыза-Ивановка', 'село Русско-Высоцкое', 'поселок городского типа Лебяжье', 'поселок городского типа Форносово', 'село Старая Ладога', 'поселок Житково', 'городской посёлок Виллози', 'деревня Лампово', 'деревня Шпаньково', 'деревня Лаврики', 'посёлок Сумино', 'посёлок Возрождение', 'деревня Старосиверская', 'посёлок Кикерино', 'поселок Возрождение', 'деревня Старое Хинколово', 'посёлок Пригородный', 'посёлок Торфяное', 'городской посёлок Будогощь', 'поселок Суходолье', 'поселок Красная Долина', 'деревня Хапо-Ое', 'поселок городского типа Дружная Горка', 'поселок Лисий Нос', 'деревня Яльгелево', 'посёлок Стеклянный', 'село Рождествено', 'деревня Старополье', 'посёлок Левашово', 'деревня Сяськелево', 'деревня Камышовка', 'садоводческое некоммерческое товарищество Лесная Поляна', 'деревня Хязельки', 'поселок Жилгородок', 'деревня Ялгино', 'поселок Новый Учхоз', 'городской посёлок Рощино', 'поселок Гончарово', 'поселок Почап', 'посёлок Сапёрное', 'посёлок Платформа 69-й километр', 'поселок Каложицы', 'деревня Фалилеево', 'деревня Пельгора', 'поселок городского типа Лесогорский', 'деревня Торошковичи', 'посёлок Белоостров', 'посёлок Алексеевка', 'поселок Серебрянский', 'поселок Лукаши', 'посёлок Песочный', 'поселок Петровское', 'деревня Щеглово', 'поселок Мичуринское', 'деревня Тарасово', 'поселок Кингисеппский', 'посёлок при железнодорожной станции Вещево', 'поселок Ушаки', 'деревня Котлы', 'деревня Сижно', 'деревня Торосово', 'поселок городского типа Токсово', 'деревня Новолисино', 'посёлок станции Громово', 'деревня Глинка', 'посёлок Мельниково', 'поселок городского типа Назия', 'деревня Старая Пустошь', 'поселок Коммунары', 'поселок Починок', 'посёлок городского типа Вознесенье', 'деревня Разбегаево', 'посёлок городского типа Рябово', 'поселок Гладкое', 'посёлок при железнодорожной станции Приветнинское', 'поселок Тёсово-4', 'посёлок Жилгородок', 'деревня Бор', 'посёлок Коробицыно', 'деревня Большая Вруда', 'деревня Курковицы', 'посёлок Лисий Нос', 'городской посёлок Советский', 'посёлок Кобралово', 'деревня Суоранда', 'поселок Кобралово', 'поселок городского типа Кондратьево', 'коттеджный поселок Счастье', 'поселок Любань', 'деревня Реброво', 'деревня Зимитицы', 'деревня Тойворово', 'поселок Семиозерье', 'поселок Лесное', 'поселок Совхозный', 'поселок Усть-Луга', 'посёлок Ленинское', 'посёлок Суйда', 'посёлок городского типа Форносово', 'деревня Нижние Осельки', 'посёлок станции Свирь', 'поселок Перово', 'Высоцк', 'поселок Гарболово', 'село Шум', 'поселок Котельский', 'поселок станции Лужайка', 'деревня Большая Пустомержа', 'поселок Красносельское', 'деревня Вахнова Кара', 'деревня Пижма', 'коттеджный поселок Кивеннапа Север', 'поселок Коробицыно', 'поселок Ромашки', 'посёлок Перово', 'деревня Каськово', 'деревня Куровицы', 'посёлок Плоское', 'поселок Сумино', 'поселок городского типа Большая Ижора', 'поселок Кирпичное', 'посёлок городского типа Павлово', 'деревня Ям-Тесово', 'деревня Раздолье', 'деревня Терпилицы', 'посёлок Шугозеро', 'деревня Ваганово', 'поселок Пушное', 'садовое товарищество Садко', 'посёлок Усть-Ижора', 'деревня Выскатка', 'городской посёлок Свирьстрой', 'поселок Громово', 'деревня Кисельня', 'посёлок Старая Малукса', 'деревня Трубников Бор', 'поселок Калитино', 'посёлок Высокоключевой', 'садовое товарищество Приладожский', 'посёлок Пансионат Зелёный Бор', 'деревня Ненимяки', 'поселок Пансионат Зелёный Бор', 'деревня Снегирёвка', 'деревня Рапполово', 'деревня Пустынка', 'поселок Рабитицы', 'деревня Большой Сабск', 'деревня Русско', 'деревня Лупполово', 'деревня Большое Рейзино', 'деревня Малая Романовка', 'поселок Дружноселье', 'поселок Пчевжа', 'поселок Володарское', 'деревня Нижняя', 'коттеджный посёлок Лесное', 'деревня Тихковицы', 'деревня Борисова Грива', 'посёлок Дзержинского'], dtype=object)
# Замечены неявные дубликаты, устраним их... приведём значения к нижнему регистру для избавления от дубликатов
data['locality_name'] = data['locality_name'].str.lower()
# Выполним замену ошибки в написании 'поселок' вместо 'посёлок':
data['locality_name'] = data['locality_name'].str.replace('поселок','посёлок')
# Замена однотипный значений
data['locality_name'] = data.locality_name.str.replace('поселок городского типа', 'поселок')
data['locality_name'] = data.locality_name.str.replace('городской поселок', 'поселок')
# Уникальные значения в алфавитном порядке
set(data['locality_name'].unique())
{nan, 'бокситогорск', 'волосово', 'волхов', 'всеволожск', 'выборг', 'высоцк', 'гатчина', 'городской посёлок большая ижора', 'городской посёлок будогощь', 'городской посёлок виллози', 'городской посёлок лесогорский', 'городской посёлок мга', 'городской посёлок назия', 'городской посёлок новоселье', 'городской посёлок павлово', 'городской посёлок рощино', 'городской посёлок свирьстрой', 'городской посёлок советский', 'городской посёлок фёдоровское', 'городской посёлок янино-1', 'деревня агалатово', 'деревня аро', 'деревня батово', 'деревня бегуницы', 'деревня белогорка', 'деревня большая вруда', 'деревня большая пустомержа', 'деревня большие колпаны', 'деревня большое рейзино', 'деревня большой сабск', 'деревня бор', 'деревня борисова грива', 'деревня ваганово', 'деревня вартемяги', 'деревня вахнова кара', 'деревня выскатка', 'деревня гарболово', 'деревня глинка', 'деревня горбунки', 'деревня гостилицы', 'деревня заклинье', 'деревня заневка', 'деревня зимитицы', 'деревня извара', 'деревня иссад', 'деревня калитино', 'деревня кальтино', 'деревня камышовка', 'деревня каськово', 'деревня келози', 'деревня кипень', 'деревня кисельня', 'деревня колтуши', 'деревня коркино', 'деревня котлы', 'деревня кривко', 'деревня кудрово', 'деревня кузьмолово', 'деревня курковицы', 'деревня куровицы', 'деревня куттузи', 'деревня лаврики', 'деревня лаголово', 'деревня лампово', 'деревня лесколово', 'деревня лопухинка', 'деревня лупполово', 'деревня малая романовка', 'деревня малое верево', 'деревня малое карлино', 'деревня малые колпаны', 'деревня мануйлово', 'деревня меньково', 'деревня мины', 'деревня мистолово', 'деревня ненимяки', 'деревня нижние осельки', 'деревня нижняя', 'деревня низино', 'деревня новое девяткино', 'деревня новолисино', 'деревня нурма', 'деревня оржицы', 'деревня парицы', 'деревня пельгора', 'деревня пеники', 'деревня пижма', 'деревня пикколово', 'деревня пудомяги', 'деревня пустынка', 'деревня пчева', 'деревня рабитицы', 'деревня разбегаево', 'деревня раздолье', 'деревня разметелево', 'деревня рапполово', 'деревня реброво', 'деревня русско', 'деревня сижно', 'деревня снегирёвка', 'деревня старая', 'деревня старая пустошь', 'деревня старое хинколово', 'деревня старополье', 'деревня старосиверская', 'деревня старые бегуницы', 'деревня суоранда', 'деревня сяськелево', 'деревня тарасово', 'деревня терпилицы', 'деревня тихковицы', 'деревня тойворово', 'деревня торосово', 'деревня торошковичи', 'деревня трубников бор', 'деревня фалилеево', 'деревня фёдоровское', 'деревня хапо-ое', 'деревня хязельки', 'деревня чудской бор', 'деревня шпаньково', 'деревня щеглово', 'деревня юкки', 'деревня ялгино', 'деревня яльгелево', 'деревня ям-тесово', 'зеленогорск', 'ивангород', 'каменногорск', 'кингисепп', 'кириши', 'кировск', 'колпино', 'коммунар', 'коттеджный посёлок кивеннапа север', 'коттеджный посёлок лесное', 'коттеджный посёлок счастье', 'красное село', 'кронштадт', 'кудрово', 'лодейное поле', 'ломоносов', 'луга', 'любань', 'мурино', 'никольское', 'новая ладога', 'отрадное', 'павловск', 'петергоф', 'пикалёво', 'подпорожье', 'посёлок александровская', 'посёлок алексеевка', 'посёлок аннино', 'посёлок барышево', 'посёлок белоостров', 'посёлок бугры', 'посёлок возрождение', 'посёлок войсковицы', 'посёлок войскорово', 'посёлок володарское', 'посёлок высокоключевой', 'посёлок гаврилово', 'посёлок гарболово', 'посёлок гладкое', 'посёлок глажево', 'посёлок глебычево', 'посёлок гончарово', 'посёлок городского типа большая ижора', 'посёлок городского типа важины', 'посёлок городского типа вознесенье', 'посёлок городского типа вырица', 'посёлок городского типа дружная горка', 'посёлок городского типа дубровка', 'посёлок городского типа ефимовский', 'посёлок городского типа имени морозова', 'посёлок городского типа имени свердлова', 'посёлок городского типа кондратьево', 'посёлок городского типа красный бор', 'посёлок городского типа кузнечное', 'посёлок городского типа кузьмоловский', 'посёлок городского типа лебяжье', 'посёлок городского типа лесогорский', 'посёлок городского типа мга', 'посёлок городского типа назия', 'посёлок городского типа никольский', 'посёлок городского типа павлово', 'посёлок городского типа приладожский', 'посёлок городского типа рахья', 'посёлок городского типа рощино', 'посёлок городского типа рябово', 'посёлок городского типа сиверский', 'посёлок городского типа синявино', 'посёлок городского типа советский', 'посёлок городского типа тайцы', 'посёлок городского типа токсово', 'посёлок городского типа ульяновка', 'посёлок городского типа форносово', 'посёлок громово', 'посёлок дзержинского', 'посёлок дружноселье', 'посёлок елизаветино', 'посёлок жилгородок', 'посёлок жилпосёлок', 'посёлок житково', 'посёлок заводской', 'посёлок запорожское', 'посёлок зимитицы', 'посёлок ильичёво', 'посёлок калитино', 'посёлок каложицы', 'посёлок кикерино', 'посёлок кингисеппский', 'посёлок кирпичное', 'посёлок кобралово', 'посёлок кобринское', 'посёлок коммунары', 'посёлок коробицыно', 'посёлок котельский', 'посёлок красная долина', 'посёлок красносельское', 'посёлок левашово', 'посёлок ленинское', 'посёлок лесное', 'посёлок лисий нос', 'посёлок лукаши', 'посёлок любань', 'посёлок мельниково', 'посёлок металлострой', 'посёлок мичуринское', 'посёлок молодцово', 'посёлок молодёжное', 'посёлок мурино', 'посёлок мыза-ивановка', 'посёлок новогорелово', 'посёлок новый свет', 'посёлок новый учхоз', 'посёлок оредеж', 'посёлок пансионат зелёный бор', 'посёлок парголово', 'посёлок первомайское', 'посёлок перово', 'посёлок песочный', 'посёлок петро-славянка', 'посёлок петровское', 'посёлок платформа 69-й километр', 'посёлок плодовое', 'посёлок плоское', 'посёлок победа', 'посёлок поляны', 'посёлок понтонный', 'посёлок почап', 'посёлок починок', 'посёлок при железнодорожной станции вещево', 'посёлок при железнодорожной станции приветнинское', 'посёлок пригородный', 'посёлок пудость', 'посёлок пушное', 'посёлок пчевжа', 'посёлок рабитицы', 'посёлок репино', 'посёлок романовка', 'посёлок ромашки', 'посёлок ропша', 'посёлок рябово', 'посёлок сапёрное', 'посёлок сапёрный', 'посёлок севастьяново', 'посёлок селезнёво', 'посёлок сельцо', 'посёлок семиозерье', 'посёлок семрино', 'посёлок серебрянский', 'посёлок совхозный', 'посёлок сосново', 'посёлок станции вещево', 'посёлок станции громово', 'посёлок станции корнево', 'посёлок станции лужайка', 'посёлок станции приветнинское', 'посёлок станции свирь', 'посёлок старая малукса', 'посёлок стеклянный', 'посёлок стрельна', 'посёлок суйда', 'посёлок сумино', 'посёлок суходолье', 'посёлок тельмана', 'посёлок терволово', 'посёлок торковичи', 'посёлок торфяное', 'посёлок тёсово-4', 'посёлок углово', 'посёлок усть-ижора', 'посёлок усть-луга', 'посёлок ушаки', 'посёлок цвелодубово', 'посёлок цвылёво', 'посёлок шугозеро', 'посёлок шушары', 'посёлок щеглово', 'приморск', 'приозерск', 'пушкин', 'садоводческое некоммерческое товарищество лесная поляна', 'садовое товарищество новая ропша', 'садовое товарищество приладожский', 'садовое товарищество рахья', 'садовое товарищество садко', 'санкт-петербург', 'светогорск', 'село копорье', 'село никольское', 'село павлово', 'село паша', 'село путилово', 'село рождествено', 'село русско-высоцкое', 'село старая ладога', 'село шум', 'сертолово', 'сестрорецк', 'сланцы', 'сосновый бор', 'сясьстрой', 'тихвин', 'тосно', 'шлиссельбург'}
# В данной колонке 49 пропущенных значений, считаю верным решением их удалить т.к. у нас нет необходимой
# информации для его заполнения, такой как координаты или подобной информации...
data = data.dropna(subset=['locality_name']) # удаление пропусков
print("Пропущенных значений:", data.locality_name.isna().sum())
# Kол-во уникальных значений после удаления дубликатов
print('Количество населенных пунктов:', data.locality_name.nunique())
Пропущенных значений: 0 Количество населенных пунктов: 329
**airports_nearest
** - расстояние до ближайшего аэропорта в метрах (м)
print(data.airports_nearest.describe())
count 18047.00000 mean 28807.62775 std 12617.14657 min 0.00000 25% 18590.00000 50% 26764.00000 75% 37294.00000 max 84869.00000 Name: airports_nearest, dtype: float64
Эти данные получены автоматически на основе картографических данных. Это, скорее всего, говорит о том, что аэропорта либо нет в данном городе, либо он находится очень далеко (свыше максимального значения для данного столбца). Таким образом, эти значения не будут существенными при определении цен на недвижимость.
Считаю верным решением, оставить все пропущенные значения - без изменений!
**city_centers_nearest
** - расстояние до центра города (м);
# Приведем имя колонки cityCenters_nearest к единому стилю
data = data.rename(columns={'cityCenters_nearest':'city_centers_nearest'})
# Меняем тип данных в колонке first_day_exposition
data.first_day_exposition = pd.to_datetime(data.first_day_exposition)
# Пробелы восполнены, типы данных приведенны в порядок
print(data.isna().sum())
data.info()
total_images 0 last_price 0 total_area 0 first_day_exposition 0 rooms 0 ceiling_height 0 floors_total 0 living_area 0 floor 0 is_apartment 0 studio 0 open_plan 0 kitchen_area 0 balcony 0 locality_name 0 airports_nearest 5498 city_centers_nearest 5475 parks_around3000 5474 parks_nearest 15501 ponds_around3000 5474 ponds_nearest 14498 days_exposition 3166 dtype: int64 <class 'pandas.core.frame.DataFrame'> Int64Index: 23545 entries, 0 to 23698 Data columns (total 22 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_images 23545 non-null int64 1 last_price 23545 non-null float64 2 total_area 23545 non-null float64 3 first_day_exposition 23545 non-null datetime64[ns] 4 rooms 23545 non-null int64 5 ceiling_height 23545 non-null float64 6 floors_total 23545 non-null int64 7 living_area 23545 non-null int64 8 floor 23545 non-null int64 9 is_apartment 23545 non-null bool 10 studio 23545 non-null bool 11 open_plan 23545 non-null bool 12 kitchen_area 23545 non-null float64 13 balcony 23545 non-null int64 14 locality_name 23545 non-null object 15 airports_nearest 18047 non-null float64 16 city_centers_nearest 18070 non-null float64 17 parks_around3000 18071 non-null float64 18 parks_nearest 8044 non-null float64 19 ponds_around3000 18071 non-null float64 20 ponds_nearest 9047 non-null float64 21 days_exposition 20379 non-null float64 dtypes: bool(3), datetime64[ns](1), float64(11), int64(6), object(1) memory usage: 3.7+ MB
Цена одного квадратного метра
data['price_per_square_meter'] = data['last_price']/data['total_area']
data.head()
total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | balcony | locality_name | airports_nearest | city_centers_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | price_per_square_meter | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20 | 13000000.0 | 108.0 | 2019-03-07 | 3 | 2.70 | 16 | 57 | 8 | False | ... | 0 | санкт-петербург | 18863.0 | 16028.0 | 1.0 | 482.0 | 2.0 | 755.0 | NaN | 120370.370370 |
1 | 7 | 3350000.0 | 40.4 | 2018-12-04 | 1 | 2.65 | 11 | 19 | 1 | False | ... | 2 | посёлок шушары | 12817.0 | 18603.0 | 0.0 | NaN | 0.0 | NaN | 81.0 | 82920.792079 |
2 | 10 | 5196000.0 | 56.0 | 2015-08-20 | 2 | 2.65 | 5 | 38 | 4 | False | ... | 0 | санкт-петербург | 21741.0 | 13933.0 | 1.0 | 90.0 | 2.0 | 574.0 | 558.0 | 92785.714286 |
3 | 0 | 64900000.0 | 159.0 | 2015-07-24 | 3 | 2.65 | 14 | 57 | 9 | False | ... | 0 | санкт-петербург | 28098.0 | 6800.0 | 2.0 | 84.0 | 3.0 | 234.0 | 424.0 | 408176.100629 |
4 | 2 | 10000000.0 | 100.0 | 2018-06-19 | 2 | 3.03 | 14 | 38 | 13 | False | ... | 0 | санкт-петербург | 31856.0 | 8098.0 | 2.0 | 112.0 | 1.0 | 48.0 | 121.0 | 100000.000000 |
5 rows × 23 columns
# Максимальная цена за квадрат
print('Максимальная цена за квадрат:', data.price_per_square_meter.max())
# Средняя цена за квадрат
print('Средняя цена за квадрат:', data.price_per_square_meter.mean())
# Медианная цена за квадрат
print('Медианная цена за квадрат:', data.price_per_square_meter.median())
# Минимальная цена за квадрат
print('Минимальная цена за квадрат:', data.price_per_square_meter.min())
Максимальная цена за квадрат: 1907500.0 Средняя цена за квадрат: 99467.63451601329 Медианная цена за квадрат: 95000.0 Минимальная цена за квадрат: 111.8348623853211
День недели, месяц, год публикации объявления
# День недели
data['day_publication'] = data['first_day_exposition'].dt.weekday
# Месяц
data['month_publication'] = data['first_day_exposition'].dt.month
# Год
data['years_publication'] = data['first_day_exposition'].dt.year
data.head()
total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | city_centers_nearest | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | price_per_square_meter | day_publication | month_publication | years_publication | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20 | 13000000.0 | 108.0 | 2019-03-07 | 3 | 2.70 | 16 | 57 | 8 | False | ... | 16028.0 | 1.0 | 482.0 | 2.0 | 755.0 | NaN | 120370.370370 | 3 | 3 | 2019 |
1 | 7 | 3350000.0 | 40.4 | 2018-12-04 | 1 | 2.65 | 11 | 19 | 1 | False | ... | 18603.0 | 0.0 | NaN | 0.0 | NaN | 81.0 | 82920.792079 | 1 | 12 | 2018 |
2 | 10 | 5196000.0 | 56.0 | 2015-08-20 | 2 | 2.65 | 5 | 38 | 4 | False | ... | 13933.0 | 1.0 | 90.0 | 2.0 | 574.0 | 558.0 | 92785.714286 | 3 | 8 | 2015 |
3 | 0 | 64900000.0 | 159.0 | 2015-07-24 | 3 | 2.65 | 14 | 57 | 9 | False | ... | 6800.0 | 2.0 | 84.0 | 3.0 | 234.0 | 424.0 | 408176.100629 | 4 | 7 | 2015 |
4 | 2 | 10000000.0 | 100.0 | 2018-06-19 | 2 | 3.03 | 14 | 38 | 13 | False | ... | 8098.0 | 2.0 | 112.0 | 1.0 | 48.0 | 121.0 | 100000.000000 | 1 | 6 | 2018 |
5 rows × 26 columns
# Посмотрим в какой год было построенно больше всего домов
data['years_publication'].value_counts()
2018 8473 2017 8153 2019 2867 2016 2746 2015 1170 2014 136 Name: years_publication, dtype: int64
Разделим на группы этажи квартир и добавим их в таблицу; варианты — первый, последний, другой
# Посторим функцию
def floor(row):
if row['floor'] == 1:
return 'первый'
if row['floor'] == row['floors_total']:
return 'последний'
else:
return 'другой'
data['floor_group'] = data.apply(floor, axis=1)
data.head()
total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | parks_around3000 | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | price_per_square_meter | day_publication | month_publication | years_publication | floor_group | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20 | 13000000.0 | 108.0 | 2019-03-07 | 3 | 2.70 | 16 | 57 | 8 | False | ... | 1.0 | 482.0 | 2.0 | 755.0 | NaN | 120370.370370 | 3 | 3 | 2019 | другой |
1 | 7 | 3350000.0 | 40.4 | 2018-12-04 | 1 | 2.65 | 11 | 19 | 1 | False | ... | 0.0 | NaN | 0.0 | NaN | 81.0 | 82920.792079 | 1 | 12 | 2018 | первый |
2 | 10 | 5196000.0 | 56.0 | 2015-08-20 | 2 | 2.65 | 5 | 38 | 4 | False | ... | 1.0 | 90.0 | 2.0 | 574.0 | 558.0 | 92785.714286 | 3 | 8 | 2015 | другой |
3 | 0 | 64900000.0 | 159.0 | 2015-07-24 | 3 | 2.65 | 14 | 57 | 9 | False | ... | 2.0 | 84.0 | 3.0 | 234.0 | 424.0 | 408176.100629 | 4 | 7 | 2015 | другой |
4 | 2 | 10000000.0 | 100.0 | 2018-06-19 | 2 | 3.03 | 14 | 38 | 13 | False | ... | 2.0 | 112.0 | 1.0 | 48.0 | 121.0 | 100000.000000 | 1 | 6 | 2018 | другой |
5 rows × 27 columns
data['floor_group'].value_counts()
другой 17352 последний 3304 первый 2889 Name: floor_group, dtype: int64
Расстояние до центра города в километрах
# Округляем
data['distance_city_center_km'] = round(data['city_centers_nearest'] / 1000)
try:
data['distance_city_center_km'] = round(data['distance_city_center_km'], 2).astype('Int32')
print('Все ОК!')
except:
print('Необходимо изменить номер бита переменной типа int')
data.head()
Все ОК!
total_images | last_price | total_area | first_day_exposition | rooms | ceiling_height | floors_total | living_area | floor | is_apartment | ... | parks_nearest | ponds_around3000 | ponds_nearest | days_exposition | price_per_square_meter | day_publication | month_publication | years_publication | floor_group | distance_city_center_km | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20 | 13000000.0 | 108.0 | 2019-03-07 | 3 | 2.70 | 16 | 57 | 8 | False | ... | 482.0 | 2.0 | 755.0 | NaN | 120370.370370 | 3 | 3 | 2019 | другой | 16 |
1 | 7 | 3350000.0 | 40.4 | 2018-12-04 | 1 | 2.65 | 11 | 19 | 1 | False | ... | NaN | 0.0 | NaN | 81.0 | 82920.792079 | 1 | 12 | 2018 | первый | 19 |
2 | 10 | 5196000.0 | 56.0 | 2015-08-20 | 2 | 2.65 | 5 | 38 | 4 | False | ... | 90.0 | 2.0 | 574.0 | 558.0 | 92785.714286 | 3 | 8 | 2015 | другой | 14 |
3 | 0 | 64900000.0 | 159.0 | 2015-07-24 | 3 | 2.65 | 14 | 57 | 9 | False | ... | 84.0 | 3.0 | 234.0 | 424.0 | 408176.100629 | 4 | 7 | 2015 | другой | 7 |
4 | 2 | 10000000.0 | 100.0 | 2018-06-19 | 2 | 3.03 | 14 | 38 | 13 | False | ... | 112.0 | 1.0 | 48.0 | 121.0 | 100000.000000 | 1 | 6 | 2018 | другой | 8 |
5 rows × 28 columns
Площадь
print('Анализ общей площади')
print(data.total_area.describe())
print('-' * 35)
print('Анализ жилой площади')
print(data.living_area.describe())
Анализ общей площади count 23545.000000 mean 60.434825 std 35.696508 min 12.000000 25% 40.000000 50% 52.000000 75% 70.000000 max 900.000000 Name: total_area, dtype: float64 ----------------------------------- Анализ жилой площади count 23545.000000 mean 39.366192 std 20.512356 min 0.000000 25% 19.000000 50% 38.000000 75% 57.000000 max 361.000000 Name: living_area, dtype: float64
print('Диаграмма размаха')
data.boxplot(column=['total_area'], figsize=(10,7), grid=True)
plt.ylabel('Площадь в квадратных метрах')
plt.show()
Диаграмма размаха
data.hist('total_area', bins=100, figsize=(10,7));
df = data.query('total_area >= 20 & total_area <= 100')
print('Диаграмма размаха площади после отсечения выбивающихся значений')
df.boxplot(column=['total_area'], figsize=(10,7), grid=True)
plt.ylabel('Площадь в квадратных метрах')
plt.show()
Диаграмма размаха площади после отсечения выбивающихся значений
df.hist('total_area', bins=100, figsize=(10,7));
Площадь кухни
data.kitchen_area.describe()
count 23545.000000 mean 9.577433 std 6.419919 min 0.000000 25% 6.400000 50% 9.000000 75% 11.500000 max 112.000000 Name: kitchen_area, dtype: float64
print('Диаграмма размаха площади кухни')
df.boxplot('kitchen_area', figsize=(10,5))
plt.ylabel('площадь кухни')
plt.show()
Диаграмма размаха площади кухни
# Предлагаю отсесь все значения выходящие от 5 до 17 м²
df = df.query('kitchen_area > 5 and kitchen_area <= 17')
print('Диаграмма размаха площади кухни')
df.boxplot('kitchen_area', figsize=(10,5))
plt.ylabel('площадь кухни')
plt.show()
df.hist('kitchen_area', bins= 100, figsize=(10,7));
Диаграмма размаха площади кухни
Цена
print('Диаграмма размаха цены предложений')
df.boxplot('last_price', figsize=(10,7))
plt.ylabel('Цена на момент снятия с публикации')
plt.show()
df.hist('last_price', bins=100, range=(0,2e+07), figsize=(10,7));
Диаграмма размаха цены предложений
df = df.query('last_price <= 9000000')
df.plot(kind='hist', y='last_price', title='Распределение стоимости квартир',grid=True, bins=50, figsize=(15,7))
plt.ylabel('количество предложений')
plt.xlabel('цена на момент снятия с публикации')
plt.show()
Количество комнат
df.plot(kind='hist',y='rooms',title='Распределение квартир по количество комнат',grid=True, bins=15, figsize=(10,5))
plt.xlabel('Количество комнат')
plt.ylabel('Количество предложений')
plt.show()
Высота потолков
print('Диаграмма размаха высоты потолков')
df.boxplot(column=['ceiling_height'], figsize=(10,5), grid=True)
plt.ylabel('Высота потолков')
plt.show()
Диаграмма размаха высоты потолков
df = df.query('ceiling_height > 2.4 and ceiling_height < 2.8')
print('Диаграмма размаха высоты потолков')
df.boxplot(column=['ceiling_height'], figsize=(10,5), grid=True)
plt.ylabel('Высота потолков')
plt.show()
Диаграмма размаха высоты потолков
df.plot(kind='hist',y='ceiling_height',grid=True, bins=50, figsize=(10,5), \
title='Распределение предложений по высоте потолков')
plt.xlabel('Высота потолоков')
plt.ylabel('Количество предложений')
plt.show()
Этаж квартиры
df.floor.describe()
count 15226.000000 mean 5.931433 std 4.811797 min 1.000000 25% 2.000000 50% 5.000000 75% 8.000000 max 27.000000 Name: floor, dtype: float64
df.plot(kind='hist',y='floor',grid=True, bins=100, figsize=(10,5), \
title='Распределение предложений по этажности')
plt.xlabel('Этаж квартиры')
plt.ylabel('Количество предложений')
plt.show()
Тип этажа квартиры («первый», «последний», «другой»)
df.pivot_table(index='floor_group', values='last_price').sort_values(by='last_price', ascending=True) \
.plot(grid=True, figsize=(10, 5),linewidth=3, title='Зависимость цены от этажа расположения квартиры')
plt.xlabel('Этаж расположения квартиры')
plt.ylabel('Цена')
plt.show()
df.pivot_table(index='floor_group',values='last_price',aggfunc='count') \
.plot.pie(y='last_price', figsize=(10,7), label='', title='График количественного отношения распределения предложений в зависимости от этажа')
plt.show()
Общее количество этажей в доме
df.boxplot(column=['floors_total'], grid=True, figsize=(10,5))
df.plot.hist(y='floors_total',grid=True, bins=100, figsize=(10,5), title='Всего этажей в доме');
Расстояние до центра города в метрах
df = df.query('city_centers_nearest > 6000 and city_centers_nearest < 22000')
df.boxplot('city_centers_nearest', figsize=(10,5));
df.hist('city_centers_nearest', bins=100, figsize=(10,5));
Расстояние до ближайшего аэропорта
df.boxplot('airports_nearest', figsize=(10,5))
df.hist('airports_nearest', bins=100, figsize=(10,5));
Расстояние до ближайшего парка
df.boxplot('airports_nearest', figsize=(10,5))
df.hist('parks_nearest', bins=100, figsize=(10,5));
День и месяц публикации объявления
df.hist('day_publication', bins=50, figsize=(10,5));
df.hist('month_publication', bins=50, figsize=(10,5));
Изучим время продажи квартиры
data.days_exposition.describe()
count 20379.000000 mean 180.839197 std 219.812620 min 1.000000 25% 45.000000 50% 95.000000 75% 231.000000 max 1580.000000 Name: days_exposition, dtype: float64
print('Время продаж')
df.boxplot('days_exposition', figsize=(10,5))
plt.ylabel('Время продажи квартиры')
plt.show()
Время продаж
df = df.query('days_exposition < 250')
print('Время продаж')
df.boxplot('days_exposition', figsize=(8,5))
plt.ylabel('Время продажи квартиры')
plt.show()
Время продаж
df.plot(kind='hist',y='days_exposition',title='Распределение количества продаж по времени', bins=100, figsize=(10,5))
plt.xlabel('Сколько дней было размещено объявление (от публикации до снятия)')
plt.ylabel('Количество объявлений')
plt.show()
Какие факторы больше всего влияют на общую (полную) стоимость объекта?
Изучим, зависит ли цена от площади, числа комнат, удалённости от центра
df['city_centers_nearest'] = df['city_centers_nearest']/1000
# Сводную таблицу: проверим зависимость цены с площадью, с количеством комнат и с расстоянием до центра
df = df.pivot_table(index='last_price', values=['total_area','rooms','city_centers_nearest'])
df.reset_index().head()
last_price | city_centers_nearest | rooms | total_area | |
---|---|---|---|---|
0 | 1200000.0 | 12.313 | 4.0 | 49.10 |
1 | 1780000.0 | 18.217 | 1.0 | 37.00 |
2 | 1800000.0 | 21.502 | 1.0 | 32.90 |
3 | 1938000.0 | 21.502 | 1.0 | 32.30 |
4 | 1961100.0 | 18.500 | 1.0 | 43.58 |
# Для наглядности построим cкаттерплот
df.plot(style='o',grid=True, figsize=(12, 5), title='График зависимости цены от площади, числа комнат, удалённости от центра')
plt.xlabel('Цена на момент снятия с публикации')
plt.ylabel('Удалённость от центра\n Число комнат\n Площадь')
plt.show()
Посчитайте среднюю цену одного квадратного метра в 10 населённых пунктах с наибольшим числом объявлений
top_10 = data.pivot_table(index='locality_name', values='first_day_exposition', aggfunc='count').sort_values(by='first_day_exposition', ascending=False).head(10)
top_10.columns=['number_of_ads']
top_10.reset_index()
locality_name | number_of_ads | |
---|---|---|
0 | санкт-петербург | 15671 |
1 | посёлок мурино | 552 |
2 | посёлок шушары | 438 |
3 | всеволожск | 398 |
4 | пушкин | 366 |
5 | колпино | 338 |
6 | посёлок парголово | 327 |
7 | гатчина | 302 |
8 | деревня кудрово | 299 |
9 | выборг | 236 |
top_10['cost_per_meter_mean'] = data.pivot_table(index='locality_name', values='price_per_square_meter', aggfunc='mean')
top_10s = top_10.sort_values(by='cost_per_meter_mean', ascending=False).reset_index()
# Построим график
fig, ax = plt.subplots(figsize=(10,5))
ax.set(title = 'Стоимость квадратного метра по населённым пунктам с ТОП-10 числом объявлений',
xlabel='населённый пункт',
ylabel='цена м2, ден.ед.')
ax.plot(top_10s['locality_name'], top_10s['cost_per_meter_mean'], 'o-')
plt.xticks(rotation='vertical')
plt.show()
# Выделим самую высокую и низкую цену
top_10s.style.format({'cost_per_meter_mean':'{:.2f} руб.'}) \
.highlight_max(color='yellowgreen', subset='cost_per_meter_mean') \
.highlight_min(color='coral', subset='cost_per_meter_mean')
locality_name | number_of_ads | cost_per_meter_mean | |
---|---|---|---|
0 | санкт-петербург | 15671 | 114836.47 руб. |
1 | пушкин | 366 | 103188.21 руб. |
2 | деревня кудрово | 299 | 92473.55 руб. |
3 | посёлок парголово | 327 | 90175.91 руб. |
4 | посёлок мурино | 552 | 85577.05 руб. |
5 | посёлок шушары | 438 | 78513.33 руб. |
6 | колпино | 338 | 75424.58 руб. |
7 | гатчина | 302 | 68846.42 руб. |
8 | всеволожск | 398 | 68654.47 руб. |
9 | выборг | 236 | 58188.87 руб. |
top_10.pivot_table(index='locality_name',values='cost_per_meter_mean') \
.plot.pie(y='cost_per_meter_mean', figsize=(7,7), legend=False, label='', \
title='Населённые пункты с самой высокой и низкой стоимостью жилья')
plt.show()
Опишите, как стоимость объектов зависит от расстояния до центра города
spb = data.loc[data['locality_name'] == 'санкт-петербург']
spb['city_centers_nearest'].isna().sum()
61
spb = spb.dropna(subset=['city_centers_nearest'])
spb['city_centers_nearest_km'] = data.distance_city_center_km
spb['city_centers_nearest_km'].describe()
count 15610.0 mean 11.596861 std 4.864728 min 0.0 25% 8.0 50% 12.0 75% 15.0 max 29.0 Name: city_centers_nearest_km, dtype: Float64
print('Диаграмма размаха удалённости от центра')
spb.boxplot('city_centers_nearest_km', figsize=(10,5))
plt.ylabel('Километры от центра города')
plt.show()
Диаграмма размаха удалённости от центра
spb = spb.loc[spb['city_centers_nearest_km'] < 24]
print('Диаграмма размаха удалённости от центра')
spb.boxplot('city_centers_nearest_km', figsize=(10,5))
plt.ylabel('Километры от центра города')
plt.show()
Диаграмма размаха удалённости от центра
mean_cost_per_km = spb['last_price'].sum()/spb['city_centers_nearest_km'].sum()
print(f'Средняя цена для каждого километра составляет {mean_cost_per_km:.0f} руб.')
Средняя цена для каждого километра составляет 702972 руб.
Построим график: он будет показывать, как цена зависит от удалённости от центра. А так же определим границу центральной зоны.
spb.pivot_table(index='city_centers_nearest_km', values='last_price', aggfunc='mean') \
.plot(figsize=(10,5), grid=True, linewidth=3, title='График зависимости цены от удалённости от центра')
plt.xlabel('Километры от центра города')
plt.ylabel('Средняя стоимость предложения')
plt.show()
spb.pivot_table(index='city_centers_nearest_km', values='last_price', aggfunc='mean') \
.plot(kind='bar', figsize=(10,5))
plt.xlabel('Километры от центра города')
plt.ylabel('Средняя стоимость предложения')
plt.show()
Анализ квартир в центре
Выделим квартиры в центре Санкт-Петербурга в отдельный срез данных.
spb_centre = spb.loc[spb['city_centers_nearest_km'] < 7]
analysis_spb = spb_centre[['locality_name','total_area','living_area','kitchen_area','last_price']].reset_index(drop=True)
analysis_spb.corr()
total_area | living_area | kitchen_area | last_price | |
---|---|---|---|---|
total_area | 1.000000 | 0.734466 | 0.454940 | 0.604567 |
living_area | 0.734466 | 1.000000 | 0.242428 | 0.303235 |
kitchen_area | 0.454940 | 0.242428 | 1.000000 | 0.324052 |
last_price | 0.604567 | 0.303235 | 0.324052 | 1.000000 |
analysis_spb.describe()
total_area | living_area | kitchen_area | last_price | |
---|---|---|---|---|
count | 3093.000000 | 3093.000000 | 3093.000000 | 3.093000e+03 |
mean | 94.902632 | 54.978985 | 13.256735 | 1.494719e+07 |
std | 60.312563 | 28.300627 | 9.630583 | 2.514233e+07 |
min | 12.000000 | 0.000000 | 0.000000 | 1.600000e+06 |
25% | 59.000000 | 38.000000 | 8.300000 | 6.800000e+06 |
50% | 80.500000 | 57.000000 | 11.500000 | 9.400000e+06 |
75% | 111.300000 | 76.000000 | 16.000000 | 1.449000e+07 |
max | 631.200000 | 361.000000 | 107.000000 | 7.630000e+08 |
pd.plotting.scatter_matrix(analysis_spb, figsize=(10, 10))
plt.show()
analysis_spb_2 = spb_centre[['last_price','rooms','floor','city_centers_nearest_km','first_day_exposition']]
pd.plotting.scatter_matrix(analysis_spb_2, figsize=(10, 10))
plt.show()
spb_centre.pivot_table(index=['rooms'], values='last_price', aggfunc=['mean', 'median']) \
.plot(kind='bar', grid=True, linewidth=3, alpha=0.6,figsize=(15,5), title='Зависимость цены от количества комнат')
plt.xlabel('Количество комнат')
plt.ylabel('Цена')
plt.show()
spb_centre.pivot_table(index=['floor_group'], values='last_price', aggfunc=['mean', 'median']) \
.plot(kind='bar', grid=True,linewidth=3, alpha=0.7,figsize=(15,5), title='Зависимость цены от этажа')
plt.xlabel('Этаж')
plt.ylabel('Цена')
plt.show()
spb_centre.pivot_table(index=['day_publication'], values='last_price', aggfunc=['mean', 'median']) \
.plot(grid=True,linewidth=3, alpha=0.7,figsize=(15,5), title='Зависимость цены от дня недели размещения объявления')
plt.xlabel('День размещения объявления')
plt.ylabel('Цена')
plt.show()
spb_centre.pivot_table(index=['month_publication'], values='last_price', aggfunc=['mean', 'median']) \
.plot(grid=True,linewidth=3, alpha=0.7,figsize=(15,5), title='Зависимость цены от месяца размещения объявления')
plt.xlabel('Месяц размещения объявления')
plt.ylabel('Цена')
plt.show()
spb_centre.pivot_table(index=['years_publication'], values='last_price', aggfunc=['mean', 'median']) \
.plot(grid=True,linewidth=3, alpha=0.7,figsize=(15,5), title='Зависимость цены от года размещения объявления')
plt.xlabel('Год размещения объявления')
plt.ylabel('Цена')
plt.show()
По результату проделанной работы по обработке и анализу недостающих данных, несомненно надо обратить внимание коллег по подготовке и сбору данных на заполняемость ячеек, либо на корректность выгрузки данных из программы. Ошибок связанных с человеческим фактором(опечаток и пр.) довольно мало, что сильно облегчило труд по предобработке материала для анализа.
После избавления пропусков и выбивающихся значений был произведен подсчёт средней стоимости квадратного метра жилья, стоимость каждого километра удаления от центра, выделен сегмент квартир расположенных в центре и проанализированы их параметры, а так же проанализирована скорость продажи объявления с даты его размещения, факторы влияющие на это и проанализирована статистика продаж в зависимости от периода размещения объявления.
Для большей наглядности при анализе использовались графики, из которых лего можно сделать вывод о том, что лидером по размещению объявлений о продаже недвижимости является "Санкт-Петербург", несмотря на это средняя цена квадратного метра приблизительно такая же как и по области. Тенденция рынка такова, что начиная с 2017 года активно растёт спрос на жильё за пределами центра Петербурга, при этом люди в основной своей массе стараются найти предложения квартир на любом этаже за исключением первого и последнего, за частую это высотные здания и новостройки. В центре же города ситуция обратная, застройки практически никакой не ведётся, этажность домов не большая и продажа квартир ведётся из старого фонда, что и отражается на снижающемся спросе начиная с 2016 года.
Резюмируя итог исследовательской работы мы имеем, что в большинстве из случаев продаются 1-2 комнатные квартиры старого фонда хрущевской застройки за полугодичный срок, со средней стоимостью в 115 т.р. за кв.метр. и высотой потолков 2,65м.. Меньше всего предложений по продаже недвижимости это в высотных новостроек по периметру Санкт-Петребурга. Таким образом можно сделать вывод о том, что количество таких домов и соответственно предложений будет расти в след за ценой в отличии от цен на жильё расположенного по центру исторического района города в невысоких домах из старого фонда.
Чек-лист готовности проекта
Поставьте 'x' в выполненных пунктах. Далее нажмите Shift+Enter.
info()
, гистограммы и т.д.)locality_name
."locality_name
и вычислите среднюю цену каждого километра. Опишите, как стоимость объектов зависит от расстояния до центра города."