Время прочтения: 8 мин.
Из-за пандемии, связанной с коронавирусной инфекцией снизился спрос на рынке коммерческой недвижимости. Проанализировав рынок, мы проверили процессы, связанные с арендой недвижимости в нашей организации. В ходе анализа мы столкнулись с трудностями использования большого массива информации из разных программ, систем и сети интернет. Нужно было уйти от ручной работы и в короткие сроки обработать большой массив информации. Готовых ИТ-решений на рынке не было. В результате мы разработали инструмент, позволяющий анализировать арендуемые и сдаваемые в аренду объекты недвижимости и сравнивать их с рынком недвижимости по нужным критериям.
В первую очередь мы провели анализ взятия и сдачи в аренду объектов недвижимости, которые напрямую относятся к нашей компании. C целью выявления случаев, аренды помещений, сопоставимых по характеристикам с теми, которые сдаем в аренду, а также расположенные на незначительном расстоянии друг от друга, мы сопоставили геолокацию объектов.
Для разработки инструмента нами был выбран язык программирования Python с применением библиотеки «Pandas» и «Pyodbc» для выгрузки информации из баз данных. Данные библиотеки использовались для предварительной подготовки данных (объединения выгрузок, отбора нужных столбцов, подготовки выборок наборов данных по интересующим параметрам) и обработки интересующего массива данных.
import pandas as pd
import pyodbc
conn = pyodbc.connect(r'Driver={SQL Server};Server=IP\database;'+
'Database= DatabaseName;Trusted_Connection=yes;'+\
'useFMTONLY=Yes;')
cur = conn.cursor()
query_db = ‘SELECT * FROM table_name1’
query_dict = ‘SELECT name1, name2… FROM table_name2’
df = pd.read_sql(query_db, conn)
dictionary = pd.read_sql(query_dict, conn)
Учитывались следующие критерии: адрес объекта, координаты, площадь объекта, цена. Также объекты были разбиты на две категории:
- Первая – это помещения, где происходило обслуживание клиентов, удаленность между такими объектами не должна превышать 500 м;
- Вторая – это складские и архивные помещения, удаленность между такими объектами не должна превышать 10-30 км.
В целом, инструмент позволяет производить расчеты и с другими произвольными численными признаками объектов недвижимости, например этажностью, при условии, что все необходимые для учета признаки будут корректно указаны в файле info.json, в котором задаются параметры запуска кластеризации. Чтобы добавить новые критерии для сравнения необходимо прописать в определенных местах файла info.json, название столбцов с необходимой информацией из базы данных. Далее файл редактируется вручную с сохранением его изначальной структуры.
info.json
{
"cluster": {
"cluster_by_coordinates": {
"_comment": "кластеризация по географическим координатам (долгота и широта), delta - максимально допустимое расстояние(км) между соседними объектами",
"dictionary_column": "name_of_column_with_objects_type",
"delta, km": {
"object_type_1": 0.5,
"object_type_2": 1,
"object_type_3": 30
},
"status": {
"delta, %": 10,
"0": "Criterion_0(from_DB)",
"1": "Criterion_1(from_DB)"
},
"coordinates": {
"Longitude(L)": "name_of_column_with_longitudes",
"Latitude(W)": " name_of_column_with_latitudes"
},
"columns_for_report": ["column1", "column2", "column3"…]
},
"cluster_by_columns": {
"_comment": "кластеризация по заданным столбцам",
"columns": ["column_1", "column_2", "column_3"…]
}
}
}
С помощью данного кода считывается информация из файла info.json:
def readjson(injson):
with codecs.open(injson, 'r', 'utf_8_sig') as f:
info = (json.load(f))
return info
def parsejson(info):
info = info['cluster']
cluster1 = info['cluster_by_coordinates']
cluster2 = info['cluster_by_columns']
dictionary_column = cluster1['dictionary_column']
deltadict = cluster1['delta']
status = cluster1['status']
deltacrit = status['delta, %']
crit0 = status['0']
crit1 = status['1']
coordinates = cluster1['coordinates']
Len = coordinates['Longitude(L)']
Wid = coordinates['Latitude(W)']
columns_for_report = cluster1['columns_for_report']
return [dictionary_column, deltadict, deltacrit, crit0, crit1, Len, Wid, columns_for_report]
Подготовка координат объектов для дальнейшего расчёта. Удаляются некорректные значения и отбираются объекты по типу назначения. Для дальнейшего удобства обработки формируются отдельные списки с координатами, индексами, статусами.
def dots_init(dictionary, database, nameON, dictionary_column, crit1, Len, Wid):
cur_objects = dictionary[dictionary[dictionary_column]==nameON]
correctind = [i for i in cur_objects.index if i in database.index]
cur_objects = database.loc[correctind]
log('Запущен процесс сбора значений долготы')
X = cur_objects[Len]
log('Запущен процесс сбора значений широты')
Y = cur_objects[Wid]
stat = {i:int(database[crit1][i]>0)
for i in cur_objects.index}
dots = []
strangearch = []
status = []
indexes = []
log('Запущен процесс подготовки координат')
for i,x,y in zip(X.index, X.values, Y.values):
if (x>0 and y>0):
dots.append([x,y])
status.append(stat[i])
indexes.append(i)
else:
strangearch.append([X[i],Y[i]])
n = (len(dots))
if n%10 == 1:
ending = 'а'
elif n%10 >= 2 and n%10 <= 9:
ending = 'ы'
else:
ending = ''
log('Подготовлено ' + str(n) + ' координат'+ending)
return cur_objects, dots, status, indexes
Сортировка координат по широте:
def sortX(dots, indexes, status):
log('Запущен процесс сортировки координат по широте')
leng = len(dots)
for i in range(leng-1):
for j in range(i+1,leng):
if dots[i][0]>dots[j][0]:
dots[i],dots[j] = dots[j],dots[i]
status[i], status[j] = status[j], status[i]
indexes[i], indexes[j] = indexes[j], indexes[i]
return dots, indexes, status
Сортировка координат по долготе:
def sortY(dots, indexes, status):
log('Запущен процесс сортировки координат по долготе')
leng = len(dots)
for i in range(leng-1):
for j in range(i+1,leng):
if dots[i][0]==dots[j][0]:
if dots[i][1]>dots[j][1]:
dots[i],dots[j] = dots[j],dots[i]
status[i], status[j] = status[j], status[i]
indexes[i], indexes[j] = indexes[j], indexes[i]
return dots, indexes, status
Далее функция calc находит для каждого сдаваемого/арендуемого объекта ближайшие арендуемые/сдаваемые объекты на заданном расстоянии и сравнивает по заданным критериям. Расположение арендуемого и сдаваемого объекта считается значимым, если заданные критерии отличаются не более, чем на 10% (параметр «delta, %» в файле info.json). Также функция calc присваивает каждому объекту определенную цветовую гамму в зависимости от полученного результата, для дальнейшего отображения на карте.
def calc(dots, delta, status, indexes, crit0, crit1, deltacrit):
onedegreex = 111*cos((68.942543+42.9846942)/2)
onedegreey = 111
col = 255
step = 0.5*delta/255 # шаг, с которым меняется яркость точки
leng = len(dots)
neighbours = [[] for i in range(leng)]
colors = [np.nan for i in range(leng)] # пустой массив цветов объектов
while col >= 0:
log('ЦВЕТ#' + str(255-col) + '/255 просчитан')
deltax = delta/onedegreex
deltay = delta/onedegreey
for i in range(len(colors)):
if np.isnan(colors[i]):
indexlist = []
j = i-1
while abs(dots[j][0] - dots[i][0]) <= deltax: # проверка разницы широт между объектами
if status[i]!=status[j]:
if abs(dots[j][1] - dots[i][1]) <= deltay: # проверка разницы долготы между объектами
indexlist.append(j)
j -= 1
if j < 0:
break
j = i+1
if j >= len(dots):
j = i
while abs(dots[j][0] - dots[i][0]) <= deltax:
if status[i]!=status[j]:
if abs(dots[j][1] - dots[i][1]) <= deltay:
indexlist.append(j)
j += 1
if j >= len(dots):
break
# происходит анализ заданных критериев, с целью выявления нужных объектов
for j in indexlist:
d1 = abs((crit0[indexes[i]]+crit1[indexes[i]])- (crit0[indexes[j]]+crit1[indexes[j]]))
d2 = max((crit0[indexes[i]]+crit1[indexes[i]]),
(crit0[indexes[j]]+crit1[indexes[j]]))
if d1/d2*100<=deltacrit:
d = sqrt(((dots[i][0]- dots[j][0])*onedegreex)**2 +
((dots[i][1]-dots[j][1])
*onedegreey)**2)
if d <= delta: # если разница критериев меньше заданной границы, объект считается значимым
colors[i] = col
neighbours[i].append(indexes[j])
delta += step
col -= 1
return colors, neighbours
Арендуемые и сдаваемые объекты отличаются по назначению. Объект может использоваться как архив, офисное помещение, склад и т.д. (в файле info.json разные типы помещений обозначены как object_type1, object_type2, object_type3 и т.д.) Расчеты для каждого типа помещений производятся отдельно, автостоянка и склад не сравниваются между собой. Соответственно, максимальное расстояние между объектами для каждого типа задается отдельно в файле info.json:
for nameON, dist in deltadict.items():
# nameON – назначение объекта (склад, гараж, архив и т.д.)
# dist – максимальное расстояние между объектами в километрах
print(nameON, dist)
log('<' + nameON + '> Начат процесс обработки')
cur_objects, dots, status, indexes = dots_init(dictionary,
database, nameON, dictionary_column, crit1, Len, Wid)
dots, indexes, status = sortX(dots, indexes, status)
dots, indexes, status = sortY(dots, indexes, status)
colors, neighbours = calc(dots, dist, status, indexes,
database[crit0], database[crit1], deltacrit)
На основании полученных результатов мы не выявили по арендуемым и сдаваемым объектам недвижимости значительных отклонений от заданных критериев. Все арендуемые и сдаваемые объекты находились друг от друга на расстояниях, соответствующих требованиям нашей организации. Но наша компания столкнулись с проблемой длительного поиска потенциальных арендаторов. Для ее решения при помощи геолокации мы сопоставили адреса своих свободных/высвобождаемых помещений с адресами дочерних/зависимых компаний, которые находятся рядом с нашими объектами и подходят по категории (офис, склад и т.д.). Для выполнения данной задачи использовали ранее разработанный инструмент, улучшенный новым алгоритмом – поиск возможных заинтересованных лиц в сети интернет с использованием «парсинга» интернет-сайтов организаций, для поиска адресов и координат объектов недвижимости:
req = requests.get('https:// адрес… ’) # получаем, код web-страницы
soup = BeautifulSoup(req.text, 'html.parser')
# извлекаем url раздела, который индивидуален для каждого города
tag_p = soup.find_all('p', attrs={'class':'color-green'})
urls = []
for p in tag_p:
for tag_a in p.find_all('a'):
urls.append(tag_a['href'])
locations = []
# извлекаем адреса представительств в каждом городе
for url in urls:
req = requests.get('https:// адрес… ’+url)
soup = BeautifulSoup(req.text, 'html.parser')
# парсим название города и адрес
city = soup.h2.string
addr = str(soup.find('div', {'class':'wrap item-grid item- grid-3'}).find_all('p')[1])
addr = re.search(u'<br/>(.+?)</p>', addr).group(1)
locations.append('{0}, {1}'.format(city, addr))
В случае, когда удавалось получить только адреса интересующих организаций, использовался код для определения координат по адресу:
addr['Url'] = ['https://www.google.com/maps/search/' + i for i in addr['Адрес']] # url для запроса координат на GoogleMap
Url_With_Coordinates = []
# настройка автоматического тестового ПО
option = webdriver.ChromeOptions()
prefs = {'profile.default_content_setting_values': {'images':2, 'javascript':2}}
option.add_experimental_option('prefs', prefs)
driver=webdriver.Chrome("D:\\ML\\sber\\chromedriver_win32\\chrome driver.exe", options=option)
# проход циклом по каждому адресу, отправляем запросу на www.google.com/maps с помощью тестового ПО
for url in tqdmn(addr.Url, leave=False):
driver.get(url)#получаем код страницы с координатами Url_With_Coordinates.append(driver.find_element_by_css_selector( 'meta[itemprop=image]').get_attribute('content'))
driver.close()
# парсим широту
lat = re.findall(r'center=(\d+.\d+)%2C',''.join(Url_With_Coordinates))
# парсим долготу
long = re.findall(r'%2C(\d+.\d+)&zoom=',''.join(Url_With_Coordinates))
Нами было выявлено, что участники экосистемы, зависимые и дочерние организации, в качестве арендаторов недвижимости, в ряде случаев не привлекаются. Мы сдавали объекты недвижимости сторонним организациям, не учитывая имеющуюся потребность у экосистемы, которая так же искала объекты на внешнем рынке.
По результатам проведенной работы нами были даны рекомендации по повышению эффективности процесса сдачи помещений в аренду, которые позволили увеличить доход от сдачи в аренду помещений, сократить потери на содержание пустующих помещений и ускорить процесс сдачи помещений в аренду.
В последующем данный алгоритм может быть использован как совместно, так и раздельно специалистами по недвижимости.
Полная версия кода доступна в репозитории на githab по ссылкам: