Restaurant Location Recommender (Using K-Means)
By: Kumar Shaswat
Tags: K Means, K Means Clustering, Unsupervised Learning, Python, Data Science

Restaurant Location Recommender (Using K-Means)

Finding suitable neighbourhoods for opening an Indian Restaurant in the capital city of India Kumar Shaswat Jan 22, 2020·8 min read

Photo by Marco Chilese on Unsplash

In ths project will try to find an optimal location for a restaurant. Specifically, this report will be targeted to stakeholders interested in opening an Indian Cusine restaurant in Delhi, India. Finding a suitable location for restaurants in major cities like Delhi proves to be a daunting task. Various factors such as over-saturation or no demand ,for the type of restaurant that the customer wants to open, effect the success or failure of the restaurant. Hence, customers can bolster their decisions using the descriptive and predictive capabilities of data science.

We need to find locations(Neighbourhood) that have a potentially unfulfilled demand for Indian Restaurant. Also, we need locations that have low competition and are not already crowded. We would also prefer location as close to popular city Neighbourhood, assuming the first two conditions are met. We will use our data science powers to generate a few most promising neighbourhoods based on this criteria. Advantages of each area will then be clearly Expressed so that best possible final location can be chosen by stakeholders.

We have required two types of data. The first was to contain the information of the neighbourhoods of Delhi. Since, our project had to analyze the neighborhoods we needed information such as a comprehensive set of neighborhood names and their locations. We were not able to find any ready-made dataset available on the internet free of cost. Therefore, we

needed to create a dataset of our own. Next, we required a dataset that contained the restaurant information for every neighbourhood. Again, as we didn’t have to the information about the neighbourhoods, we were not able to obtain a ready-made dataset.

To create the neighborhood dataset we have used the data from a Wikipedia page that contained the the names of the neighborhoods segregated we boroughs . To extract the data from the web page we created a web scrapper using the beautifulSoup library mentioned above. After extracting the relevant information from the web page, we store the result in a pandas dataframe.

#accessing the web page by Http request made by requests library

req = requests.get("https://en.wikipedia.org/wiki/Neighbourhoods_of_Delhi").text

soup = BeautifulSoup(req, 'lxml')

div = soup.find('div', class_="mw-parser-output" )

print("web Page Imported") #Code to extract the relevent data from the request object using beautiful soup

data = pd.DataFrame(columns=['Borough','Neighborhood'])

i=-1

flag = False

no=0

prev_borough = None

for child in div.children:

if child.name:

span = child.find('span')

if span!=-1 and span is not None:

try:

if span['class'][0] == 'mw-headline' and child.a.text!='edit':

prev_borough = child.a.text

i+=1

flag = True

continue

except KeyError:

continue

if child.name=='ul' and flag==True:

neighborhood = []

for ch in child.children:



try:

data.loc[no]=[prev_borough,ch.text]

no+=1

except AttributeError:

pass

flag = False

data[50:60]

Still, there was no information about the location of these neighborhoods. Therefore, to obtain the location data for each neighborhood we use the geopy. Geocoder webservice call. We loop over the pandas dataframe and geocode each neighborhood one by one. After we are finished with the geocoding, we observe that location data for a few neighborhoods was still missing from the dataset. To complete the dataset we manually searched and updated the coordinates for these neighborhoods over the internet.

lat_lng = pd.DataFrame(columns=['latitude','longitude'])

geolocator = Nominatim(user_agent="ny_explorer")

for i in range(184):

address = data['Neighborhood'].loc[i]+',New Delhi'

try:

location = geolocator.geocode(address)

lat_lng.loc[i]=[location.latitude,location.longitude]

except AttributeError:

continue

df1 = data

df2 = lat_lng

delhi_neighbourhood_data = pd.concat([df1, df2], axis=1)

Neighborhood dataset

neighborhood map

Now that we have the location data for all the neighborhoods in Delhi, foursquare api can be called for obtaining restaurant data for each neighborhood. We use the ‘explore’ call with the coordinate of each neighborhood, and the filter code for restaurants to receive only the data for nearby ‘restaurants’. When the API call is made we receive the data as JSON (JavaScript Object Notation) object. We now extract the attributes that are necessary for our analysis.

The features selected were:

· Venue

· Venue Category

· Venue Latitude

· Venue Longitude

After converting the selected data into a dataframe, we export and save in csv format. Finally, we visualize the data for better comprehension.

def getNearbyVenues(names, latitudes, longitudes, radius=500):



venues_list=[]

for name, lat, lng in zip(names, latitudes, longitudes):

print(name)



# create the API request URL

url = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&categoryId={}&ll={},{}&radius={}&limit={}'.format(

CLIENT_ID,

CLIENT_SECRET,

VERSION,

'4d4b7105d754a06374d81259',

lat,

lng,

radius,

LIMIT)



# make the GET request

try:

results = requests.get(url).json()["response"]['groups'][0]['items']

except KeyError:

continue



# return only relevant information for each nearby venue

venues_list.append([(

name,

lat,

lng,

v['venue']['name'],

v['venue']['location']['lat'],

v['venue']['location']['lng'],

v['venue']['categories'][0]['name']) for v in results])



nearby_venues = pd.DataFrame([item for venue_list in venues_list for item in venue_list])

nearby_venues.columns = ['Neighborhood',

'Neighborhood Latitude',

'Neighborhood Longitude',

'Venue',

'Venue Latitude',

'Venue Longitude',

'Venue Category']

return(nearby_venues)

restaurant dataset

restaurant map

Our goal here is to find the neighborhoods with low density of Indian restaurants. But, how will we decide which neighborhoods, currently operating on minimal number of Indian restaurants, have the potential for growth and which neighborhoods do not.

The most intuitive idea would be to find neighborhoods that have similar patterns of restaurant trends.

This can be achived by clustering the neighborhoods of the basis of the restaurant data we have acquired. Clustering is a predominant algorithm of unsupervised Machine Learning. It is used to segregate data entries in cluster depending of the similarity of their attributes, calculated by using the simple formula of euclidian distance.

We can then analyze these clusters separately and use those clusters that show high trends of Indian Restaurants.

# one hot encoding

delhi_onehot = pd.get_dummies(delhi_venues[['Venue Category']], prefix="", prefix_sep="")



# add neighborhood column back to dataframe

delhi_onehot['Neighborhood'] = delhi_venues['Neighborhood']



# move neighborhood column to the first column

fixed_columns = [delhi_onehot.columns[-1]] + list(delhi_onehot.columns[:-1])

delhi_onehot = delhi_onehot[fixed_columns]



delhi_onehot.head() # set number of clusters

kclusters = 5



delhi_grouped_clustering = delhi_grouped.drop('Neighborhood', 1)



# run k-means clustering

kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(delhi_grouped_clustering)



# check cluster labels generated for each row in the dataframe

kmeans.labels_[0:10] # add clustering labels

neighborhoods_venues_sorted.insert(0, 'Cluster Labels', kmeans.labels_)



delhi_merged = delhiData



# merge toronto_grouped with toronto_data to add latitude/longitude for each neighborhood

delhi_merged = delhi_merged.join(neighborhoods_venues_sorted.set_index('Neighborhood'), on='Neighborhood')



delhi_merged.dropna(inplace=True)

delhi_merged.head() # check the last columns!

Color-coded map of neighborhoods divided in clusters

After we have segregated the dataset in clusters, we analyze them to gain a better insight. We plot bar graphs for each neighborhood to get a better insight on the distribution paradigm of restaurants in each cluster. Our goal is to find neighborhoods that have a demand for Indian restaurants. Therefore, we can look through each cluster and pick the top two clusters that have the highest number. This allows us to eliminate all the neighborhoods that probably won’t require a new Indian restaurant; depending on the current trends. The bar graphs of each cluster and shown below. Analysing the bar graphs we can clearly see that clusters 2 and 3 have a high demand for Indian Restaurants.

forheatmap=delhi_venues

forheatmap=pd.merge(forheatmap,delhi_merged[['Neighborhood','Cluster Labels']],left_on='Neighborhood', right_on='Neighborhood',how='inner')

forheatmap.drop(forheatmap[~forheatmap['Cluster Labels'].isin([2,3])].index, inplace=True)

We know that when we were clustering the neighborhoods the data used contained the mean of all types of restaurants present in the particular neighborhood. Therefore, we can say that the neighborhoods are clustered on their restaurant trends.

Now, clusters 2 and 3 may collectively have the highest number of Indian restaurant but there will be some neighborhoods in these clusters which would have a demand for Indian Restaurants, as these neighborhoods are in the same cluster, but would not have enough supply. To find these restaurants, we will plot two heat maps. The first, will render the data about the location of all the restaurants in the chosen clusters. The second, will contain the data about only the location of Indian restaurants in these clusters.

Heat map of all restaurants

Heat map of Indian Restaurants

After analysis of the heat maps, we remove the over-crowded neighborhoods. Examining all the neighborhoods we have and the restaurants they contain we conclude to the following elimination conditions:

· Number of Indian restaurants >30%

· Number of all restaurants >60%

# selecting neighborhoods with condition 1

count_all = forheatmap[['Neighborhood','Venue']].groupby('Neighborhood').count().sort_values(by='Venue')

target_count = int(0.6*len(count_all))

print(count_all.iloc[target_count])

count_all.drop(count_all[count_all.Venue.values>7].index,inplace=True)

count_all.columns=['all count']

# selecting neighborhoods with condition 2

y('Neighborhood').count().sort_values(by='Venue')

target_count = int(0.3*len(count_ind))

print(count_ind.iloc[target_count])

count_ind.drop(count_ind[count_ind.Venue.values>1].index,inplace=True)

count_ind.columns = ['ind count'] #merging

lowdensity = count_all.join(count_ind) #making final dataset

temp_recommend = delhiData

temp_recommend.drop(temp_recommend[~temp_recommend['Neighborhood'].isin(lowdensity.index.values)].index, inplace=True)

temp_recommend.head()

Now, to apply the final condition, we first need to find out the three most popular neighborhoods. This can be easily done by arranging all the neighborhoods with respect to the number of restaurants in each of their vicinity and selecting the top three. Once we have chosen the most popular neighborhoods, we calculate the distance of all the other neighborhoods from all three. Finally, we select the top five closets neighborhoods to each of the top three neighborhoods and inner join the resulting dataset to give out the final list of recommended neighborhoods.

#most popular neighborhoods

top_nei = delhi_venues[['Neighborhood','Venue']].groupby('Neighborhood').count().sort_values(by='Venue', ascending=False).head(3).index.values

top_nei >>array(['Connaught Place', 'Khirki Village', 'Hauz Khas Village'],

dtype=object) final_recommend=neiNearCP.append(neiNearHK).append(neiNearKV).reset_index()

final_recommend.drop_duplicates(inplace=True)

final_recommend.reset_index(inplace=True)

final_recommend.drop(columns=['index'],inplace=True)

final_recommend

Final Recommended Neighborhoods

Our Analysis was done on over 186 neighborhoods, containing over 848 restaurants within 2km radius of every neighborhood. We segragated these neighborhoods on the basis of types and amounts of restaurants. Five clusters were obtained, each having a unique collection of restaurants. Since, we were focused on finding optimal neighborhoods for opening Indian restaurants, we selected cluster 2 and 3 which had the highest number of Indian restaurants. The above actions left us with the only those neighborhoods that had a shared characteristics of and that had a high demand for Indian restaurants.

Next, we plotted a heat map for analysing the density of restaurants in the remaining neighborhoods. This allowed us to select neighborhoods that had few or no Indian restaurants and were not overcrowded by other kinds of restaurants. A total of 57 neighborhoods were left. After this, we found out the top three most popular neighborhoods(namely: Canaught Place, Hauz khas Village and Khirki Village), and the distance of every remaining neighborhoods from all three of them. Then, we extracted top 5 closest neighborhoods from each of three most popular neighborhoods mentioned above. Taking the union of the resulting three dataset we get 11 neighborhoods that satisfy all three conditons layed out in the business problem by the customer.

The neighborhoods recommendation obtained here are not completely accurate. This is due to the limitations in the dataset used in the project. Due to lack of cross referencing sources, we may have missed a few neighborhoods from our consideration. The foursquare API does not contain, or does not rely, a comprehensive dataset about the restaurants present in delhi. Surely, in a city like Delhi with a population of over 19 million, there are much more restaurants than 848.