供应链选址-- 基于服务水平优化DC数量_自定义






前言


上一篇文章我们分析了如何根据客户的分布和DC的数量来确认DC仓的位置分布, 在文中我们采用广义的kmeans来进行聚类。

​供应链选址(1)-基于自定义距离的广义Kmeans 聚类​

这篇文章我们接着来探讨如何优化DC的数量



服务水平

我们通过引入服务水平来确定所需的DC个数,假如,每天货车行驶距离为350km,我们承诺客户下单之后三日送达,那么如果DC到客户的距离在三日里程之内,该客户就会满意。

如果想要更多的客户满意,就需要更多的DC仓,但是西部个别省份地广人稀,设立一个DC仅仅服务于个别客户可能不是一个好的决策。

一般,企业会设定阈值,比如95%作为服务水平,也就是满足95%客户的需求。




service_level = 0.95
distance_threshold = 3*350



引入数据

为了让我们的代码更贴合实际,我们选择更多的客户数量,比如500,进行演示。



import pandas as pd
random_seed = 2021
all_cities_df = pd.read_csv(file,encoding='gb18030')
customer_df = all_cities_df.sample(n=500,random_state=random_seed)
customer_df.columns = ['id','name','lon','lat']
print(customer_df)

预览随机抽取的客户信息如下:

供应链选址-- 基于服务水平优化DC数量_自定义_02







计算服务水平

服务水平的计算思路很直观:

  1. 根据选定的DC数量以及位置,计算客户到所分配的DC的距离
  2. 如果该客户到DC的距离大于设定的阈值,就是无法满足服务。
  3. 统计满意的客户占比,即为服务水平

通过经纬度计算距离的函数在上一篇文章已经介绍。另外,通过经纬度获取真实地址的函数也已经介绍,这里就不再展示。我们拍拍脑袋,先看看20个DC能达到什么样的服务水平




kclusters = 20
kmedoids_custom = KMedoids(n_clusters=kclusters, random_state=12,metric=geo_distance).fit(customer_df[['lon','lat']])
dc_kmedoids_custom_df = pd.DataFrame(kmedoids_custom.cluster_centers_,columns=['lon','lat'])
dc_kmedoids_custom_df = dc_kmedoids_custom_df.apply(get_city, axis=1)
customer_df['dc'] = kmedoids_custom.labels_
all_df = pd.merge(left=customer_df,right=dc_kmedoids_custom_df,left_notallow='dc',right_index=True,suffixes=('_customer','_dc'))
all_df = all_df.apply(geo_distance_dc,axis=1)
all_df.head()

预览计算的DC到客户的距离,看样子还不错,少于350km。

供应链选址-- 基于服务水平优化DC数量_自定义_03

我们进一步画出这些客户到对应DC的距离分布图


all_df['distance'].hist()
calc_service_level = (sum(all_df['distance'] <=distance_threshold))/all_df.shape[0]
print(f'服务水平为:{calc_service_level*100:.2f}%')

可以看到服务水平达到了97.6%。看来20个DC的数量还是能达到很高的客户满意度,来满足全国范围内三日达,多数客户到DC的距离会小于400 km。


供应链选址-- 基于服务水平优化DC数量_自定义_04


优化DC数量

上面的服务水平满足要求,但是是否是最优的呢?假如每个DC建立的成本和每年维护的费用很高,DC越多代表投入越多。另外,如前面所说,如果从经济性来考虑,如果一个DC的存在只是为了满足少量客户,这不会有很好的投入产出比。

我们要想办法,如何(恰好)满足95%的服务水平?

思路也很直观,迭代计算:

  1. 我们从最少的DC数量开始尝试,确定分布后计算总体服务水平,然后判断是否达到要求。
  2. 如果没有达到95%的要求,我们进一步增加DC的数量,重新分配和计算。

当然了,这里为了简单采用的是总体的服务水平,实际中也可能要求每个DC的服务水平都要达到95%,这个一样可以采用该方法进行优化计算,只不过最后的统计口径不同而已。

代码如下:


max_num_DC = 20
DC_QTY = 1
dc_num_series = pd.Series()
for k in range(1,max_num_DC,1):
temp_df = customer_df.copy()
# set num to n_clusters
kmedoids_custom = KMedoids(n_clusters=k, random_state=12,metric=geo_distance).fit(customer_df[['lon','lat']])
dc_df = pd.DataFrame(kmedoids_custom.cluster_centers_,columns=['lon','lat'])
dc_df = dc_df.apply(get_city, axis=1)
temp_df['dc'] = kmedoids_custom.labels_
temp_df = pd.merge(left=temp_df,right=dc_df,left_on='dc',right_index=True,suffixes=('_customer','_dc'))
temp_df = temp_df.apply(geo_distance_dc,axis=1)
calc_service_level = (sum(temp_df['distance'] <=distance_threshold))/temp_df.shape[0]
dc_num_series.loc[k] = calc_service_level
print('**************')
print(f'DC 数量为:{k}')
print(f'服务水平为:{calc_service_level*100:.2f}%')
DC_QTY = k
if calc_service_level > service_level:
break

供应链选址-- 基于服务水平优化DC数量_聚类_05

供应链选址-- 基于服务水平优化DC数量_迭代_06

可以看到当DC数量为7的时候,我们就能满足95%的服务水平,所以这时候就没必要考虑设立20个DC了,浪费钱。

意/与不满意客户分布

以上我们已经分析了如何选择合适的DC数量以及确定他们分布。为了更好的展示我们的分析结果,我们一样可以通过map来展示信息。相比于第一篇的plot,我们这里多添加一些细节:

1. 将DC的标记换成更显眼的icon,比如castle

2. 将不同DC管辖(服务)的客户用不同的颜色进行区分

3. 将不满意客户的用不同(更大)的marker size 进行区分。

可以看到DC无法三日达的客户都分布在边疆,这就是为啥淘宝不包邮新疆,西藏了,因为DC数量不够啊



总结

本文简单介绍了服务水平,并且通过引入服务水平,来优化选址项目中DC的数量。通过简单粗暴的迭代方法,我们可以得到比较靠谱的DC数量。

最后我们将分析的结果展示在map上,并且明显的区分DC的辖区,以及每个DC每个客户的满足度。

后续我们可以会考虑一些其他因素,进一步优化选址的方案。