python

Стоимость жилья как функция координат

  • вторник, 1 сентября 2015 г. в 02:12:51
http://habrahabr.ru/post/265783/


Цены на жильё формируются из многочисленных факторов, основные из которых — это близость к центру города и наличие рядом различной инфраструктуры. Но реальные цены только в бумажных газетах и риэлторских сайтах. Мы будем строить свою карту с ценами на недвижимость в Москве при помощи python, яндекс API и matplotlib, специальный репортаж с места событий под катом.

Гипотеза


Как человек, не проживающий в москве, характер цен в москве оцениваю следующим образом:
  • очень дорого — в пределах садового кольца
  • дорого — от садового кольца до ТТК
  • не очень дорого — между ТТК и МКАД, а цена линейно убывает в сторону МКАДа
  • дешево — за МКАДом

На карте будут присутствовать локальные максимумы и минимумы в связи с близостью важных объектов или промышленных зон. А также будет разрыв в ценах до МКАДа и после, т.к. это кольцо в основном совпадает с административной границей города.

Сотни строчек великолепного и не очень python-кода будут доступны в конце статьи по ссылке.

Для исследований я взял два сайта о недвижимости с данными за лето этого года. Всего в выборке участвовали 24,000 записей о новостройках и вторичном жилье, причём разные объявления с одним адресом усреднялись по цене.

Объявления парсились скриптом и хранились в sqlite базе данных в формате:
широта, долгота, цена за кв.м.

О веб пауках
Да, из-за недостатка знаний, никаких сторонних библиотек не было использовано и это повлекло за собой создание двух отдельных скриптов по одному на каждый сайт, дёргающих адреса, метраж и стоимость квартир. Адреса волшебным образом превращались в координаты посредством Google Geocoder API. Но из-за довольно низкой планки квоты на использование был вынужден запускать скрипт каждый день в течении недели. Геокодер яндекса в 10 раз бесплатнее .


Строим функцию


Для обобщения функции на всю плоскость её необходимо проинтерполировать по имеющимся точкам. Для этого подойдёт функция LinearNDInterpolator из модуля scipy. Для этого только необходимо установить python с наобором научных библиотек, известный как scipy. В случае, когда данные очень разнородны, практически невозможно подобрать правдоподобную функцию на плоскости. Метод LinearNDInterpolator использует триангуляцию Делоне, разбивая всю плоскость на множество треугольников.

Важный фактор, который нужно учитывать при построении функций — разброс значений функции. Среди объявлений попадаются настоящие монстры с ценой за квадратный метр больше 10 млн. рублей внутри кремля, они испортят график и вы увидите лишь однородное поле с яркой точкой. Для того, чтобы на графике можно было различить практически все данные, такие значения нужно отфильтровывать границей, подбираемой эмпирическим путём. Для статистической модели эти значения не несут полезной информации.

А тем временем, результат интерполяции выглядит как градиентный ад (кликабельно):



Чтобы получить удобную для восприятия карту, нужно распределить полученные значения на дискретные уровни. После чего карта становится похожей на страничку из атласа за 7й класс (кликабельно):



О дискретизации на карте
В зависимости от того, хотим мы видеть общую картину цен или флуктуации вблизи среднего значения, необходимо применить компадирование данных, т.е. распределение данных равномернее на шкалу значений, уменьшая больше значения и увеличивая маленькие. В коде это выглядит так:
    zz = np.array(map(lambda x: map(lambda y: int(2*(0.956657*math.log(y) - 10.6288)) , x), zz)) #HARD    
    zz = np.array(map(lambda x: map(lambda y: int(2*(0.708516*math.log(y) - 7.12526)) , x), zz)) #MEDIUM  
    zz = np.array(map(lambda x: map(lambda y: int(2*(0.568065*math.log(y) - 5.10212)) , x), zz)) #LOW  

Функции подбирались эмпирическим путём при помощи аппроксимации по 3м-4м точкам на wolframalpha.


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

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



Расчёты похожи на расчёт гравитационного поля в точке. Для оптимизации будем брать в расчёт только те точки, которые действительно вносят вклад в конечное значение поля. После расчётов, результат напоминает брызги (кликабельно).



Какое ещё преобразование?
При строгом построении графика поля, на нём видно россыпь точек, соответствующих локальному преобладанию “дорогого” поля над “недорогим” и наоборот. Эти точки похожи на шум и портят график. Убрать их можно, например, медианным фильтром над изображением с достаточном большим значением. Для этого я использовал командный интерфейс программы IrfanView.


Визуализация


Совмести полученные изображения со схематичной картой Москвы. Яндекс API позволяет взять карту по координатам и указать для неё угловые размеры по долготе и широте, а так же желаемый размер изображения.

Пример запроса:
http://static-maps.yandex.ru/1.x/?ll=37.5946002,55.7622764&spn=0.25,0.25&size=400,400&l=map

Проблема заключается только в том, что указанные угловые размеры определяют не границы видимой области, а её гарантируемый размер. Это значит, что мы получим картинку с угловыми размерами >= 0.25. Способа совладать с границами видимых координат обнаружено не было, и они искались вручную.

О подгониане
Выровнять карты друг относительно друга можно при помощи яндекс меток, рисуя точки на карте с заданными координатами и получая карту с метками.

За пару вызовов из библиотеки PIL изображения совмещаются с комфортными для наблюдения уровнями прозрачности.

map_img = Image.open(map_img_name, 'r').convert('RGBA')
price_img = Image.open(prices_img_name, 'r').convert('RGBA')
if price_img.size == map_img.size:
    result_img = Image.blend(map_img, price_img, 0.5)


Результаты


Три изображения с разными уровнями компандирования и анимация варианта с полем.



Немного аналитики:

В целом, как и предсказывала гипотеза, внутри садового кольца и кольца ТТК цены на жильё максимальны и убывают по мере удаления от центра. Однако в пределах МКАД, средняя цена сохраняется в западной и юго-западной частях. За пределами МКАД, а так же в восточной части за ТТК цена ниже средней.

В деталях всё намного интересней, отметим основные области:
  • В лужниках и воробьёвых горах жить довольно дорого жилой недвижимости в области воробьёвых гор нет, скорее все область была построена по граничным значениям сверху и снизу
  • Жилые массивы вблизи фундаментальной библиотеки МГУ, строящиеся и построенные высотки у мосфильмовского пруда стоят дороже предположительно из-за активной стройки и обширных лесопарковых участков. Высокая цена на территории от мемориальной синагоги и сквером им. Анны Герман так же обусловлена окружающими ценами и своим местоположением среди парков и заказника.
  • В районе между станцией метро “Крылатское” и проспектом Маршала Жукова жильё так же считается дорогим
  • Несмотря на положение за МКАДом и близостью к кладбищу, дома вдоль улицы Генерала Белобородова отличаются высокой ценой.

Как видно на картах, теория вполне подтверждается практикой и удачное сочетание инфраструктуры, расстояния до центра и близости к известным московским сооружениям будет выявлено функцией линейной интерполяции над координатами.

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

Код веб пауков, самой программы, а так же использованные базы данных доступны через GitHub.