Using Weighted K-Means Clustering to Determine Distribution Centres Locations | by Pararawendy Indarjo | Towards Data Science

Background
All of us must be familiar with fast-food chain companies such as KFC or McDonalds, due to their massive number of outlets surrounding us. Such names are well known for offering ready-to-eat meals speedily, thanks to advance-bulk cooking and sometimes also pre-packaging. As an implication of their business model, fast-food chains require fast supply chain execution, particularly for the ingredients. This is to ensure they are always fresh to be served to the customers.
Regarding this, have you ever been thinking about how they could provide such perishable ingredients promptly? This turns out not a simple problem as it may sound, due to the vast magnitude of the number of outlets involved. In this respect, distribution centers portrayed by warehouses take a critical role.
Fast-food chains’ warehouses are the place where the suppliers drop off their goods, which later packaged, and finally distributed to the outlets. Typically, a warehouse takes care of all outlets within a couple of cities around it. Because of this, fast-food chains would have several warehouses spread across their operating regions.
In this article, we consider a global fast-food company that recently planned to enter the Indonesian market. The management expects to execute this expansion in two phases: Phase I and Phase II. The first phase will cover only Java Island, while the rest regions will be handled on Phase II. To this end, we will help to determine its warehouses’ locations, specifically for Phase I.
We will help a fast-food restaurant to determine their warehouses’ locations spreading across Java Island.
Recalling (Standard) K-Means Clustering
K-means clustering is an algorithm for partitioning the data into K distinct clusters. The high-level view on how the algorithm works is as follows. Given a (typically random) initiation of K clusters (which implied from K centroids), the algorithm iterates between two steps below:
Computing the distances between all data points and the existing K centroids and re-assigning each data point to its nearest centroid accordingly.Calculating the new K centroids, by taking the data points’ mean, based on this new clustering assignment.
Computing the distances between all data points and the existing K centroids and re-assigning each data point to its nearest centroid accordingly.
Calculating the new K centroids, by taking the data points’ mean, based on this new clustering assignment.
The above iteration is executed until the centroids do not change over iterations (algorithm converged) or a specific stopping criterion has been satisfied (e.g., max number of iterations is triggered)
As the readers might have known, the number K in this algorithm is a hyperparameter (i.e., predefined number). In practice, we can select the optimal K by using, for example, the Elbow method, which we will use and explain later in the article.
Moreover, there is also a degree of freedom in picking the distance metric, rather than the standard Euclidean distance used in the original algorithm. If necessary or seen more appropriate, we can use other distance metrics such as Haversine distance (the one we will use in the article) or Manhattan distance.
Solving the problem using weighted K-means clustering
Let’s go back to our problem! Determining the warehouses’ locations can be seen as finding centroids of clusters of the corresponding served branches. Therefore, this is an excellent use case of K-means clustering, specifically weighted K-means clustering. What we mean on ‘weighted’ will be apparent in a moment.
We have already known in the first place that the fast-food company will open branches across Java. We can, therefore, address the problem by first identifying the branch’s locations, which we can reasonably approximate with the longitude-latitude — comparable to (x,y) — of all cities/districts in Java. Using these data points of city centers, we can actually run the standard K-means clustering to solve the problem already. Yet, this approach would fail to take into account the fact that some cities are indeed bigger than others, which implies having a higher volume demand for ingredients to be supplied.
The standard K-means approach would fail to take into account the fact that some cities are indeed bigger than others, which implies having a higher volume demand for ingredients to be supplied.
In light of this, we also include population data for becoming proxy to ingredient demands. These population data will then be the weight of corresponding cities (data points) and voila! We have had the setting to run the weighted K-means clustering algorithm.
To give an interpretation of the influence of the weights on the algorithm, imagine we are now working with data points with different sizes (represented by their weights). We can further see the size to be proportional to the gravitational force owned by the data point. Thus, the bigger the weight is, the stronger a data point pulls the centroid to be closer to it. In summary, the ultimate difference from the standard algorithm is on centroid calculation, where now weighted averages are used, instead of standard means.
The data sources
We are grateful to this repository of benangmerah GitHub account for providing the data of longitude-latitude coordinates of the considered cities. It turns out that there are 119 cities in Java Island, from Cilegon in the west to Banyuwangi in the left part close to Bali. Moreover, we refer to BPS (Indonesian Bureau of Statistics) for the cities breakdown population data within each Java province in 2018.
Haversine Distance
Note that we will be working with longitude-latitude, which represents the earth’s spherical surface — as opposed to the standard plain 2D coordinates system in (x,y). Because of this, the Euclidean distance is not the best distance metric to use here. Instead, we will use the Haversine distance, which is an appropriate distance metric on a spherical surface.
The following equation where φ is latitude, λ is longitude, R is earth’s radius (mean radius = 6,371km) is how we calculate distance using the Haversine method (notate as d).
a = sin2(( φB — φA) /2) + cos φA * cos φB * sin2((λB — λA) /2)
c = 2 * atan2( √a, √(1−a) )
d = R ⋅ c
An R implementation of this distance metric is given below.
#haversine distance functionhaversine_dist = function(point1, point2) { #each argument is a numeric vector with two elements (lon, lat)  lon1 = point1[1]   lat1 = point1[2]  lon2 = point2[1]  lat2 = point2[2]    R = 6371000 #earth radius in meters  phi1 = lat1 * pi / 180 #convert to radian  phi2 = lat2 * pi / 180 #convert to radian  delta_phi = (lat2 - lat1) * pi / 180  delta_lambda = (lon2 - lon1) * pi / 180    a = (sin(delta_phi/2))^2 + cos(phi1) * cos(phi2) * ((sin(delta_lambda/2))^2)  c = 2 * atan2(sqrt(a), sqrt(1-a))    distance = R * c #haversine distance between point1 and point 2 in meters  return(round(distance, 2))}
Step by step solution and codes in R
Ok, let’s hands-on with the code! In this article, we will use R to run the algorithm.
Data preparation
We import a DataFrame that contains the required columns: city name, longitude, latitude, and population.
df_city = read.csv('/Users/parara/Documents/Project/Weighted K-Means/city_data.csv')head(df_city)
Initialization
We fix the number of clusters K, say 5. Next, we randomly choose five cities to become the initial centroids. Afterward, using these centroids, we assign each city to its nearest centroid (initial cluster). Recall that we use Haversine distance as the distance metric.
#number of clustersK = 5#initial centroids by randominit_centroids_index = sample(nrow(df_city),K)#initiate containersdistance_matrix = matrix(data = NA, nrow = nrow(df_city), ncol = K)cluster = vector()centroid_long = vector()centroid_lat = vector()#compute distance between cities and initial centroidsfor (k in c(1:K)) {  for (i in c(1:nrow(df_city))) {    city_i = as.numeric(df_city[i,2:3])    centroid_k = as.numeric(df_city[init_centroids_index[k],2:3])    distance_matrix[i,k] = haversine_dist(city_i,centroid_k)  }}#initial cluster assignment for each cityfor (i in c(1:nrow(df_city))) {  cluster[i] = which.min(distance_matrix[i,])}
The looping
Having the initial clusters, we are ready to carry out the looping. We iteratively update centroids and re-assign cities to clusters, which we do until cluster assignments are unchanged between iterations.
#iteration baselineold_cluster = vector(length = length(cluster))new_cluster = cluster#iterationswhile (!all(old_cluster == new_cluster)) {  #update old cluster assignment  old_cluster = new_cluster  #calculate centroids using weighted average  for (k in c(1:K)) {    cluster_k = which(old_cluster == k) #city index of cluster k    centroid_long[k] = weighted.mean(df_city$longitude[cluster_k], df_city$population[cluster_k])    centroid_lat[k] = weighted.mean(df_city$latitude[cluster_k], df_city$population[cluster_k])  }  df_centroid = as.data.frame(cbind(centroid_long, centroid_lat))  #compute distance between cities and centroids  for (k in c(1:K)) {    for (i in c(1:nrow(df_city))) {      city_i = as.numeric(df_city[i,2:3])      centroid_k = as.numeric(df_centroid[k,])      distance_matrix[i,k] = haversine_dist(city_i,centroid_k)    }  }  #update cluster assignment for each city  for (i in c(1:nrow(df_city))) {    cluster[i] = which.min(distance_matrix[i,])  }  #update new_cluster  new_cluster = cluster}
Choosing the best K
The truth is, the value of K = 5 selected in the above is made retrospectively. We had beforehand picked the number based on the following elbow method. But, understanding the technique requires us first to comprehend what so-called sum of squares error (SSE). In the K-means algorithm, the term “error” in SSE refers to the deviation between a data point to its centroid. Therefore, we get the value of SSE by summing all of such squared errors for all data points.
We note that any clusters assignment corresponds to an SSE value. Thus, we can plot the SSE values of the converged algorithms at various K (number of clusters). Using this plot, we look at K at which the decline slopes after the value are no longer significant. This is an optimal K we are looking for. Typically, this K value would form an ‘elbow shape’ on the overall plot hence the name.
For ours, the plot is given below. Note that the decline of SSE is no longer significant after K = 5 and hence we pick this number as the optimal K.
Result
Finally, the result of the algorithm is given below.
Based on the result above, our recommendation of five warehouses locations is thus as follows:
Near Depok, West JavaNear Majalengka, West JavaNear Temanggung, Central JavaNear Madiun, East JavaNear Probolinggo, East Java
Near Depok, West Java
Near Majalengka, West Java
Near Temanggung, Central Java
Near Madiun, East Java
Near Probolinggo, East Java
Conclusion and remark
In this article, we have demonstrated an application of the weighted K-means clustering algorithm to determine warehouses’ locations of a fast-food restaurant. To summarize, this modified version of K-means differs from the original one in the way it calculates the clusters’ centroids, which uses the weighted average instead of the regular mean.
A final remark remains regarding our implementation (codes given in this post). K-means algorithm yields only local optimum instead of the global one. That is, the final cluster assignment obtained depends on the initial assignment, which is random. Therefore, in the real application, we need to replicate the whole algorithm several times and pick results from the best replication that yields the smallest SSE.
For interested readers, I put the code for this ‘real implementation’ in my GitHub repository here. There, you can also find the codes for SSE function, generating plots displayed in this post, and of course, the dataset used in this article.
All in all, thank you for reading and let’s connect with me on LinkedIn at https://www.linkedin.com/in/pararawendy-indarjo-5b87bab7.