Кластеризация ночной жизни города с помощью машинного обучения

Кластеризация KMeans с использованием API Foursquare

Введение

Всем известно, как пандемия Covid-19 опустошила индустрию ночной жизни социальным дистанцированием, блокировками, ношением масок и ранним комендантским часом. Эти места для ночных развлечений были закрыты, потому что они считались второстепенными услугами и местами легкой передачи коронавируса. Теперь, когда центральное правительство и правительство штата в Индии ослабили ограничения, люди, наконец, могут насладиться передышкой, отмечая особое событие или просто проводя время с друзьями за едой и напитками.

В таком городе, как Пуна, который может похвастаться бурлящей ночной жизнью, где-то всегда проходит вечеринка. Широко известный как «ИТ-центр Индии», «Автомобильный и производственный центр Индии» и «Оксфорд Востока», Пуна известна своим стилем жизни, приятной погодой и просто… всем хорошим. С увеличением количества пабов, баров, лаунж-баров и подобных тусовок ночная жизнь в Пуне стала еще более захватывающей. В Пуне много баров, клубов, пабов для пар, компаний друзей, а также полуночников.

Любому новому посетителю в Пуне может быть трудно понять, где ему следует провести вечеринку и выпить. В этой статье мы постараемся сгруппировать город в районы или пригороды с высокой концентрацией баров и пабов.

Исследование окрестностей

Для кластеризации нам потребуется список окрестностей Пуны, их широта и долгота.

Получить соседей

Википедия — отличный источник информации. Список районов Пуны был взят с помощью библиотеки Python BeautifulSoup с указанной страницы Википедии.

data = requests.get("https://en.wikipedia.org/wiki/Category:Neighbourhoods_in_Pune").text
soup = BeautifulSoup(data, 'html.parser')
neighbors = []
for row in soup.find_all("div",  {"class": "mw-category"})[0].findAll("li"):
    neighbors.append(row.text)
    
#Create a new DataFrame from the list
df_neighbors = pd.DataFrame({"neighbor": neighbors})
df_neighbors = df_neighbors.drop(labels=[26,32,39, 40], axis=0)
df_neighbors.reset_index(drop=True)

Получить координаты соседей

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

## Function to fetch the geo coordinates for the locations
def getNeighborCoords(neighbor):
    lat_lng_coords = None
    
    while(lat_lng_coords is None):
        geo = geocoder.arcgis('{}, Pune, India'.format(neighbor))
        lat_lng_coords = geo.latlng
    
    return lat_lng_coords

Используя приведенную выше функцию, мы получим координаты для каждой из соседних областей. Результирующий вывод функции отображается как новые столбцы, то есть «широта» и «долгота».

coords = [ getNeighborCoords(neighbor) for neighbor in df_neighbors["neighbor"].tolist()]
## Map the latitude and longitude retuned from the function 
df_neighbors['latitude'], df_neighbors['longitude'] = zip(*coords)

Наш окончательный кадр данных теперь имеет соседа и его соответствующие координаты широты и долготы.

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

## center of pune
pune_center = [18.5204303, 73.8567437]
## plot the neighbors on the map using Folium library
map_pune = folium.Map(location=pune_center, zoom_start=11)
for neighbor, lat, lon in  zip(df_neighbors['neighbor'],df_neighbors['latitude'],df_neighbors['longitude']):
    label = '{}'.format(neighbor)
    label = folium.Popup(label, parse_html=True)
    folium.CircleMarker([lat, lon], radius=5, color='black', fill_color='blue', fill_opacity=0.25, popup=label, fill=True).add_to(map_pune)
    
map_pune

Исследование площадок

Теперь, когда у нас есть районы Пуны с соответствующей информацией о широте и долготе, давайте получим окружающие места в пределах заданного радиуса (км).

Получить места

Foursquare API, социальная служба определения местоположения, позволяет пользователям узнавать о компаниях и достопримечательностях. Поскольку мы пытаемся найти бары, я добавил идентификатор категории 4bf58dd8d48988d116941735 как часть URL-адреса запроса API. Я определил лимит мест, возвращаемых на 100 в радиусе 1 км.

url = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&categoryId=4bf58dd8d48988d116941735&v={}&ll={},{}&radius={}&limit={}&offset={}'.format(
            CLIENT_ID, 
            CLIENT_SECRET, 
            VERSION, 
            latitude, 
            longitude, 
            RADIUS, 
            LIMIT, offset)

С помощью вызова API Foursquare мы получили около 168 площадок.

Инжиниринг данных

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

df_Venues = getNearbyVenues(df_neighbors['neighbor'],df_neighbors['latitude'],df_neighbors['longitude'])
df_Venues.dropna(how='any', inplace=True)
df_Venues.reset_index(drop=True, inplace=True)
df_Venues.head()

Кроме того, в пределах заданного радиуса 1 км существует высокая вероятность того, что API Foursquare вернет те же места для соседей, которые находятся в непосредственной близости. Давайте найдем и удалим повторяющиеся места. В этом процессе мы сохраним «первое» вхождение повторяющихся записей и удалим остальные.

df_Venues.drop_duplicates(subset=['name','categories','lat','lng'], keep='first', inplace=True)

Это привело к удалению повторяющихся площадок, и теперь у нас осталось 113 площадок. Пока отлично! Теперь давайте рассмотрим уникальные места, возвращаемые API Foursquare.

df_Venues['categories'].unique()

Вы видите, что уникальный список включает в себя все виды заведений, включая Juice Bars, Falafel Restaurants, Jazz Club, Cafe. и т. д. Хотя не уверен, что во всех этих заведениях подают алкоголь, но для простоты и лучшей группировки давайте просто сосредоточимся на нескольких известных категориях заведений.

## filter out only Bars from the fetched venues
df_hotels = df_Venues[df_Venues['categories'].isin(['Bar', 'Hotel Bar','Pub', 'Cocktail Bar', Lounge','Sports Bar', 'Gastropub','Brewery','Distillery']) ]
df_hotels.reset_index(drop=True, inplace=True)
## rearrange and rename the columns
df_hotels.columns=['neighbor','latitude','longitude','venue','category','venue_lat','venue_lon']
df_hotels.head()

Кластеризация соседей и площадок

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

Сначала мы применим метод горячего кодирования к столбцу «категория»; возьмите среднюю частоту для каждой категории мест и, наконец, выполните кластеризацию K-средних, алгоритм обучения без учителя, используемый для создания кластеров K точек данных на основе сходства признаков.

## one-hot encode the category
df_hotels_OHE = pd.get_dummies(df_hotels['category'], prefix='', prefix_sep='')
df_hotels_OHE['neighbor'] = df_hotels['neighbor']
## rearrange the columns with 'neighbor' as first column.
fixed_columns = [df_hotels_OHE.columns[-1]] + list(df_hotels_OHE.columns[:-1])
df_hotels_OHE = df_hotels_OHE[fixed_columns]
df_hotels_grouped = df_hotels_OHE.groupby('neighbor').mean().reset_index()
df_hotels_grouped.head()

Используя этот сгруппированный фрейм данных, мы удалим соседний столбец и применим метод кластеризации K-средних, чтобы получить оптимальное значение «k». Глядя на метод локтя и оценку силуэта, кажется, что оптимальное количество кластеров для использования составляет K = 8.

Теперь давайте выполним фактическую кластеризацию K-средних, используя k = 8 в наборе данных, и сопоставим метки кластера с каждым соседом.

kclusters = 8 
x = df_hotels_grouped.drop(['neighbor'], 1)​
# run k-means clustering
kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(x)​
df_hotels_merged["cluster"] = kmeans.labels_
df_clustered = pd.merge(df_hotels, df_hotels_merged[['neighbor','cluster']] ,on=["neighbor"])

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

occurences = folium.map.FeatureGroup()
map_clusters = folium.Map(location=pune_center, zoom_start=11)
# Setting color scheme for the clusters
x = np.arange(kclusters)
ys = [i+x+(i*x)**2 for i in range(kclusters)]
colors_array = cm.rainbow(np.linspace(0, 1, len(ys)))
rainbow = [colors.rgb2hex(i) for i in colors_array]
for lat, lon, poi, cluster in zip(df_clustered['latitude'], df_clustered['longitude'],                                df_clustered['neighbor'], df_clustered['cluster']):
    
    label = folium.Popup(str(poi) + ' - Cluster ' + str(cluster), parse_html=True)
    
    folium.CircleMarker([lat,lon],
                        radius=5,
                        popup=label,
                        color=rainbow[cluster-1],
                        fill=True,
                        fill_color=rainbow[cluster-1],
                        fill_opacity=1,
                        icon = folium.Icon(icon='beer', prefix='fa')).add_to(map_clusters)
map_clusters

Ниже представлена ​​карта, показывающая кластеры для разных районов. Точки данных, выделенные разными цветами, представляют собой сгруппированные области.

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

Для дальнейшего изучения и дополнительного понимания кластеризации, вот тепловая карта кластеризованных соседей. Если вы посмотрите на район, Виманнагар показывает высокую плотность, тогда как центральные районы Пуны имеют среднюю плотность.

df_neighbor_HM = df_clustered.groupby(['neighbor','latitude','longitude'])['category'].count().reset_index()
pune_latlons = df_neighbor_HM[['latitude','longitude','category']]
pune_map = folium.Map(location=pune_center, zoom_start=10)
folium.TileLayer('cartodbpositron').add_to(pune_map) #cartodbpositron cartodbdark_matter
HeatMap(pune_latlons, blur=10).add_to(pune_map)
folium.Marker(pune_center).add_to(pune_map)
pune_map

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

pune_latlons = df_clustered[['venue_lat','venue_lon']]
pune_map = folium.Map(location=pune_center, zoom_start=13)
folium.TileLayer('cartodbpositron').add_to(pune_map) #cartodbpositron cartodbdark_matter
HeatMap(pune_latlons, blur=20).add_to(pune_map)
folium.Circle(pune_center, radius=2000, fill=False, color='white').add_to(pune_map)
folium.Circle(pune_center, radius=4000, fill=False, color='white').add_to(pune_map)
pune_map

Вывод

В заключение мы использовали кластеризацию K-средних, которая создала 8 различных кластеров для соседей Пуны, вокруг которых расположены ночные клубы. Вокруг Виманнагара, Кальяни-Нагара густонаселено большое количество ночных клубов (в радиусе 1 км). Эти районы находятся в непосредственной близости от ИТ-центров, и, следовательно, эти места являются хорошими источниками дохода.

Кроме того, если вы видите центральную часть Пуны, в районах кемпинга также есть множество мест для ночных вечеринок. Удивительно, но в районах Пимпри-Чинчвад, Банер, Вакад, которые находятся недалеко от парка Хинджевади (крупный IT-хаб), похоже, не так много площадок. Вероятно, это может быть из-за данных/местоположений соседей ИЛИ небольшого количества мест/мест, зарегистрированных на Foursquare.

Итак, если вы посещаете Пуну впервые ИЛИ планируете вечеринку, вы можете использовать эту карту, чтобы получить хорошее представление о том, где можно выпить.

Приятных вечеринок! Пейте ответственно 🍻 🍸 🍷

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