Find a location for opening a restaurant

Shokhrukh Yakubjanov
Analytics Vidhya
Published in
12 min readJun 26, 2021

With machine learning K-mean algorithm to cluster locations.

Photo by Iconfinder

Introduction: Business Problem

In this project we will try to find an optimal location for a restaurant. Specifically, this report will be targeted to stakeholders interested in opening a Turkish restaurant (as I’m big fan of turkish cuisine) in Tashkent, Uzbekistan.

Since there are lots of restaurants in Tashkent we will try to detect locations that are not already crowded with restaurants (especially south side of the city). We are also particularly interested in areas with no Turkish restaurants in vicinity. We would also prefer locations as close to city center as possible, assuming that first two conditions are met.

We will use our data science powers to generate a few most promising neighborhoods based on this criteria. Advantages of each area will then be clearly expressed so that best possible final location can be chosen by stakeholders.

About Data

Based on definition of our problem, factors that will influence our decission are:

  • number of existing restaurants in the neighborhood (any type of restaurant)
  • number of and distance to Turkish restaurants in the neighborhood, if any
  • distance of neighborhood from city center

We decided to use regularly spaced grid of locations, centered around city center, to define our neighborhoods.

Following data sources will be needed to extract/generate the required information:

  • centers of candidate areas will be generated algorithmically and approximate addresses of centers of those areas will be obtained using Google Maps API reverse geocoding
  • number of restaurants and their type and location in every neighborhood will be obtained using Foursquare API
  • coordinate of Tashkent center will be obtained using Google Maps API geocoding of well known Tashkent location

Neighborhood Candidates

Let’s create latitude & longitude coordinates for centroids of our candidate neighborhoods. We will create a grid of cells covering our area of interest which is approx. 12x12 kilometers centered around Tashkent city center.

Let’s first find the latitude & longitude of Tashkent center, using specific, well known address and Google Maps geocoding API:

import requestsdef get_coordinates(api_key, address, verbose=False):
try:
url = 'https://maps.googleapis.com/maps/api/geocode/json?key={}&address={}'.format(api_key, address)
response = requests.get(url).json()
if verbose:
print('Google Maps API JSON result =>', response)
results = response['results']
geographical_data = results[0]['geometry']['location'] # get geographical coordinates
lat = geographical_data['lat']
lon = geographical_data['lng']
return [lat, lon]
except:
return [None, None]

address = 'Tashkent, Uzbekistan'
tashkent_center = get_coordinates(google_api_key, address)
print('Coordinate of {}: {}'.format(address, tashkent_center))

Now let’s create a grid of area candidates, equaly spaced, centered around city center and within ~6km from Tashkent. Our neighborhoods will be defined as circular areas with a radius of 300 meters, so our neighborhood centers will be 600 meters apart.

To accurately calculate distances we need to create our grid of locations in Cartesian 2D coordinate system which allows us to calculate distances in meters (not in latitude/longitude degrees). Then we’ll project those coordinates back to latitude/longitude degrees to be shown on Folium map. So let’s create functions to convert between WGS84 spherical coordinate system (latitude/longitude degrees) and UTM Cartesian coordinate system (X/Y coordinates in meters).

!pip install shapely
import shapely.geometry
!pip install pyproj
import pyproj
import mathdef lonlat_to_xy(lon, lat):
proj_latlon = pyproj.Proj(proj='latlong',datum='WGS84')
proj_xy = pyproj.Proj(proj="utm", zone=33, datum='WGS84')
xy = pyproj.transform(proj_latlon, proj_xy, lon, lat)
return xy[0], xy[1]
def xy_to_lonlat(x, y):
proj_latlon = pyproj.Proj(proj='latlong',datum='WGS84')
proj_xy = pyproj.Proj(proj="utm", zone=33, datum='WGS84')
lonlat = pyproj.transform(proj_xy, proj_latlon, x, y)
return lonlat[0], lonlat[1]
def calc_xy_distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
return math.sqrt(dx*dx + dy*dy)
print('Coordinate transformation check')
print('-------------------------------')
print('Tashkent center longitude={}, latitude={}'.format(tashkent_center[1], tashkent_center[0]))
x, y = lonlat_to_xy(tashkent_center[1], tashkent_center[0])
print('Tashkent center UTM X={}, Y={}'.format(x, y))
lo, la = xy_to_lonlat(x, y)
print('Tashkent center longitude={}, latitude={}'.format(lo, la))

Let’s create a hexagonal grid of cells: we offset every other row, and adjust vertical row spacing so that every cell center is equally distant from all neighbors.

tashkent_center_x, tashkent_center_y = lonlat_to_xy(tashkent_center[1], tashkent_center[0]) # City center in Cartesian coordinatesk = math.sqrt(3) / 2 # Vertical offset for hexagonal grid cells
x_min = tashkent_center_x - 6000
x_step = 600
y_min = tashkent_center_y - 6000 - (int(21/k)*k*600 - 12000)/2
y_step = 600 * k
latitudes = []
longitudes = []
distances_from_center = []
xs = []
ys = []
for i in range(0, int(21/k)):
y = y_min + i * y_step
x_offset = 300 if i%2==0 else 0
for j in range(0, 21):
x = x_min + j * x_step + x_offset
distance_from_center = calc_xy_distance(tashkent_center_x, tashkent_center_y, x, y)
if (distance_from_center <= 6001):
lon, lat = xy_to_lonlat(x, y)
latitudes.append(lat)
longitudes.append(lon)
distances_from_center.append(distance_from_center)
xs.append(x)
ys.append(y)
print(len(latitudes), 'candidate neighborhood centers generated.')

Let’s visualize the data we have so far: city center location and candidate neighborhood centers:

OK, we now have the coordinates of centers of neighborhoods/areas to be evaluated, equally spaced (distance from every point to it’s neighbors is exactly the same) and within ~6km from Tashkent center.

Let’s now use Google Maps API to get approximate addresses of those locations:

Let’s now place all this into a Pandas dataframe:

Foursquare

Now that we have our location candidates, let’s use Foursquare API to get info on restaurants in each neighborhood.

We’re interested in venues in ‘food’ category, but only those that are proper restaurants — coffee shops, pizza places, bakeries etc. are not direct competitors so we don’t care about those. So we will include in out list only venues that have ‘restaurant’ in category name, and we’ll make sure to detect and include all the subcategories of specific ‘Turkish restaurant’ category, as we need info on Turkish restaurants in the neighborhood.

# Category IDs corresponding to Turkish restaurants were taken from Foursquare web site (https://developer.foursquare.com/docs/resources/categories):food_category = '4d4b7105d754a06374d81259' # 'Root' category for all food-related venuesturkish_restaurant_categories = ['4f04af1f2fb6e1c99f3db0bb','530faca9bcbc57f1066bc2f3','530faca9bcbc57f1066bc2f4',
'5283c7b4e4b094cb91ec88d8','5283c7b4e4b094cb91ec88d9','5283c7b4e4b094cb91ec88db',
'5283c7b4e4b094cb91ec88d6','56aa371be4b08b9a8d573535','56aa371be4b08b9a8d5734bd',
'5283c7b4e4b094cb91ec88d5','5283c7b4e4b094cb91ec88da','530faca9bcbc57f1066bc2f2',
'58daa1558bbb0b01f18ec1df','58daa1558bbb0b01f18ec1dc','56aa371be4b08b9a8d5734bf',
'56aa371be4b08b9a8d5734c1','5283c7b4e4b094cb91ec88d4','58daa1558bbb0b01f18ec1e2']
def is_restaurant(categories, specific_filter=None):
restaurant_words = ['restaurant', 'diner', 'doner', 'steakhouse']
restaurant = False
specific = False
for c in categories:
category_name = c[0].lower()
category_id = c[1]
for r in restaurant_words:
if r in category_name:
restaurant = True
if 'fast food' in category_name:
restaurant = False
if not(specific_filter is None) and (category_id in specific_filter):
specific = True
restaurant = True
return restaurant, specific
def get_categories(categories):
return [(cat['name'], cat['id']) for cat in categories]
def format_address(location):
address = ', '.join(location['formattedAddress'])
address = address.replace(', Uzbekistan', '')
address = address.replace(', Uzbekistan', '')
return address
def get_venues_near_location(lat, lon, category, client_id, client_secret, radius=500, limit=100):
version = '20180605'
url = 'https://api.foursquare.com/v2/venues/explore?client_id={}&client_secret={}&v={}&ll={},{}&categoryId={}&radius={}&limit={}'.format(
client_id, client_secret, version, lat, lon, category, radius, limit)
try:
results = requests.get(url).json()['response']['groups'][0]['items']
venues = [(item['venue']['id'],
item['venue']['name'],
get_categories(item['venue']['categories']),
(item['venue']['location']['lat'], item['venue']['location']['lng']),
format_address(item['venue']['location']),
item['venue']['location']['distance']) for item in results]
except:
venues = []
return venues
# Let's now go over our neighborhood locations and get nearby restaurants; we'll also maintain a dictionary of all found restaurants and all found turkish restaurantsimport pickledef get_restaurants(lats, lons):
restaurants = {}
turkish_restaurants = {}
location_restaurants = []
print('Obtaining venues around candidate locations:', end='')
for lat, lon in zip(lats, lons):
# Using radius=350 to meke sure we have overlaps/full coverage so we don't miss any restaurant (we're using dictionaries to remove any duplicates resulting from area overlaps)
venues = get_venues_near_location(lat, lon, food_category, client_id, client_secret, radius=350, limit=100)
area_restaurants = []
for venue in venues:
venue_id = venue[0]
venue_name = venue[1]
venue_categories = venue[2]
venue_latlon = venue[3]
venue_address = venue[4]
venue_distance = venue[5]
is_res, is_turkish = is_restaurant(venue_categories, specific_filter=turkish_restaurant_categories)
if is_res:
x, y = lonlat_to_xy(venue_latlon[1], venue_latlon[0])
restaurant = (venue_id, venue_name, venue_latlon[0], venue_latlon[1], venue_address, venue_distance, is_turkish, x, y)
if venue_distance<=300:
area_restaurants.append(restaurant)
restaurants[venue_id] = restaurant
if is_turkish:
turkish_restaurants[venue_id] = restaurant
location_restaurants.append(area_restaurants)
print(' .', end='')
print(' done.')
return restaurants, turkish_restaurants, location_restaurants

Let’s now see all the collected restaurants in our area of interest on map, and let’s also show Turkish restaurants in different color:

Turkish restaurants marked as red other restaurants blue color

Looking good. So now we have all the restaurants in area within few kilometers from Tashkent center, and we know which ones are Turkish restaurants! We also know which restaurants exactly are in vicinity of every neighborhood candidate center.

This concludes the data gathering phase — we’re now ready to use this data for analysis to produce the report on optimal locations for a new Turkish restaurant!

Methodology

n this project we will direct our efforts on detecting areas of Tashkent that have low restaurant density, particularly those with low number of Turkish restaurants. We will limit our analysis to area ~6km around city center.

In first step we have collected the required data: location and type (category) of every restaurant within 6km from Tashkent center . We have also identified Turkish restaurants (according to Foursquare categorization).

Second step in our analysis will be calculation and exploration of ‘restaurant density’ across different areas of Tashkent — we will use heatmaps to identify a few promising areas close to center with low number of restaurants in general (and no Turkish restaurants in vicinity) and focus our attention on those areas.

In third and final step we will focus on most promising areas and within those create clusters of locations that meet some basic requirements established in discussion with stakeholders: we will take into consideration locations with no more than two restaurants in radius of 250 meters, and we want locations without Turkish restaurants in radius of 400 meters. We will present map of all such locations but also create clusters (using k-means clustering) of those locations to identify general zones / neighborhoods / addresses which should be a starting point for final ‘street level’ exploration and search for optimal venue location by stakeholders.

Analysis

Let’s perform some basic explanatory data analysis and derive some additional info from our raw data. First let’s count the number of restaurants in every area candidate:

OK, now calculate the distance to nearest Turkish restaurant from every area candidate center (not only those within 300m — we want distance to closest one, regardless of how distant it is).

So on average Turkish restaurant can be found within ~2km from every area center candidate. That’s fairly close, so we need to filter our areas carefully!

Let’s create a map showing heatmap / density of restaurants and try to extract some meaningful info from that:

Let’s create another heatmap map showing heatmap/density of Turkish restaurants only.

This map is not so ‘hot’ (Turkish restaurants represent a subset of ~4% of all restaurants in Tashkent) but it also indicates some density of existing Turkish restaurants directly west and south-east from Tashkent center, with closest pockets of low Turkish restaurant density positioned south-east and south from city center.

Based on this we will now focus our analysis on areas south, south-east and west from Tashkent center — we will move the center of our area of interest and reduce it’s size to have a radius of 2.5km. This places our location candidates mostly in boroughs with large low restaurant density south-east from city center, however this borough is less interesting to stakeholders as it’s mostly residential and less popular with tourists).

Define new, more narrow region of interest, which will include low-restaurant-count parts closest to Tashkent center:

Also create new, more dense grid of location candidates restricted to our new region of interest (let’s make our location candidates 100m apart).

2261 candidate neighborhood centers generated

OK. Now let’s calculate two most important things for each location candidate: number of restaurants in vicinity (we’ll use radius of 250 meters) and distance to closest Turkish restaurant:

def count_restaurants_nearby(x, y, restaurants, radius=250):    
count = 0
for res in restaurants.values():
res_x = res[7]; res_y = res[8]
d = calc_xy_distance(x, y, res_x, res_y)
if d<=radius:
count += 1
return count
def find_nearest_restaurant(x, y, restaurants):
d_min = 100000
for res in restaurants.values():
res_x = res[7]; res_y = res[8]
d = calc_xy_distance(x, y, res_x, res_y)
if d<=d_min:
d_min = d
return d_min
roi_restaurant_counts = []
roi_turkish_distances = []
print('Generating data on location candidates... ', end='')
for x, y in zip(roi_xs, roi_ys):
count = count_restaurants_nearby(x, y, restaurants, radius=250)
roi_restaurant_counts.append(count)
distance = find_nearest_restaurant(x, y, turkish_restaurants)
roi_turkish_distances.append(distance)
print('done.')

Let’s put this into dataframe:

Now let’s filter those locations: we’re interested only in locations with no more than two restaurants in radius of 250 meters, and no Turkish restaurants in radius of 400 meters:

Let’s see how this looks on a map:

Looking good. We now have a bunch of locations fairly close to Tashkent center and we know that each of those locations has no more than two restaurants in radius of ~2km, and also no Turkish restaurant closer than `2km. Any of those locations is a potential candidate for a new Turkish restaurant, at least based on nearby competition.

Now let’s cluster those locations to create centers of zones containing good locations. Those zones, their centers and addresses will be the final result of our analysis.

from sklearn.cluster import KMeansnumber_of_clusters = 15good_xys = df_good_locations[['X', 'Y']].values
kmeans = KMeans(n_clusters=number_of_clusters, random_state=0).fit(good_xys)
cluster_centers = [xy_to_lonlat(cc[0], cc[1]) for cc in kmeans.cluster_centers_]map_tashkent = folium.Map(location=roi_center, zoom_start=14)
folium.TileLayer('cartodbpositron').add_to(map_tashkent)
HeatMap(restaurant_latlons).add_to(map_tashkent)
folium.Circle(roi_center, radius=2500, color='white', fill=True, fill_opacity=0.4).add_to(map_tashkent)
folium.Marker(tashkent_center).add_to(map_tashkent)
for lon, lat in cluster_centers:
folium.Circle([lat, lon], radius=500, color='green', fill=True, fill_opacity=0.25).add_to(map_tashkent)
for lat, lon in zip(good_latitudes, good_longitudes):
folium.CircleMarker([lat, lon], radius=2, color='blue', fill=True, fill_color='blue', fill_opacity=1).add_to(map_tashkent)
map_tashkent
Clusters represent groupings of most of the candidate locations and cluster centers are placed nicely in the middle of the zones ‘rich’ with location candidates.

Finally, let’s reverse geocode those candidate area centers to get the addresses which can be presented to stakeholders:

This concludes our analysis. We have created 15 addresses representing centers of zones containing locations with low number of restaurants and no Turkish restaurants nearby, all zones being fairly close to city center (all less than 4km from Tashkent center, and about half of those less than 2km from Tashkent center). Although zones are shown on map with a radius of ~500 meters (green circles), their shape is actually very irregular and their centers/addresses should be considered only as a starting point for exploring area neighborhoods in search for potential restaurant locations.

Results and Discussion

Our analysis shows that although there is a great number of restaurants in Tashkent (~200 in our initial area of interest which was 12x12km around Tashkent center), there are pockets of low restaurant density fairly close to city center. Highest concentration of restaurants was detected north and west from Tashkent center, so we focused our attention to areas south, south-east and east, corresponding to boroughs.

After directing our attention to this more narrow area of interest (covering approx. 5x5km south-east from Tashkent center) we first created a dense grid of location candidates (spaced 290m apart); those locations were then filtered so that those with more than two restaurants in radius of 600m and those with an Turkish restaurant closer than 820m were removed.

Those location candidates were then clustered to create zones of interest which contain greatest number of location candidates. Addresses of centers of those zones were also generated using reverse geocoding to be used as markers/starting points for more detailed local analysis based on other factors.

Result of all this is 15 zones containing largest number of potential new restaurant locations based on number of and distance to existing venues — both restaurants in general and Turkish restaurants particularly. This, of course, does not imply that those zones are actually optimal locations for a new restaurant! Purpose of this analysis was to only provide info on areas close to Tashkent center but not crowded with existing restaurants (particularly Turkish) — it is entirely possible that there is a very good reason for small number of restaurants in any of those areas, reasons which would make them unsuitable for a new restaurant regardless of lack of competition in the area. Recommended zones should therefore be considered only as a starting point for more detailed analysis which could eventually result in location which has not only no nearby competition but also other factors taken into account and all other relevant conditions met.

Conclusion

Purpose of this project was to identify Tashkent areas close to center with low number of restaurants (particularly Turkish restaurants) in order to aid stakeholders in narrowing down the search for optimal location for a new Turkish restaurant. By calculating restaurant density distribution from Foursquare data we have first identified general boroughs that justify further analysis, and then generated extensive collection of locations which satisfy some basic requirements regarding existing nearby restaurants. Clustering of those locations was then performed in order to create major zones of interest (containing greatest number of potential locations) and addresses of those zone centers were created to be used as starting points for final exploration by stakeholders.

Final decision on optimal restaurant location will be made by stakeholders based on specific characteristics of neighborhoods and locations in every recommended zone, taking into consideration additional factors like attractiveness of each location (proximity to park or water), levels of noise / proximity to major roads, real estate availability, prices, social and economic dynamics of every neighborhood etc.

References

  1. Foursquare API
  2. Google Maps API

--

--

Shokhrukh Yakubjanov
Analytics Vidhya

I’m a certified IBM data scientist , examining new and more convincing methods for data analysis, visualization and data modeling.