作者 | Francesca Picache 编译 | VK 来源 | Towards Data Science
由于COVID-19,菲律宾大马尼拉市自2020年3月以来一直处于隔离状态。检疫限制令大多数企业难以经营,但也极大地增加了配送服务的需求,因为外出公共场所不安全。
由于大量的送货费用对大多数顾客没有吸引力,一些商家在某些地区(通常是专属村庄)如果有很多顾客,就会提供免费送货服务。这些企业通常在每个村庄都有一个Viber聊天群,那里的居民发送他们的订单。
这种配送模式适用于销售蔬菜、海鲜和肉类等重要易腐商品的企业,因为这些商品经常被同一客户购买。因此,将这些货物运送到大马尼拉的村庄是这次大流行期间可能存在的商机之一。
目标市场
本报告的目标人群是那些有兴趣在菲律宾大马尼拉地区的偏远村庄开展新鲜农产品配送业务的利益相关者。
业务问题
如果一周6天送货,人们怎么知道每天要把货物送到哪个村庄以优化物流,又怎么知道从哪个市场供应商那里获得新鲜农产品?
1.数据
根据问题的定义,以下是将影响决策的因素:
下列数据来源将用于提取或生成所需的资料:
2.方法
这个国家的一家顶级面包店每周都会给大马尼拉最高档的村庄送面包。由于客户必须选择自己居住的村庄,他们的在线订单中包含了送货目的地。
我记录了这45个地区,并将每个村庄的经度和纬度合并到一个CSV文件中,这是我使用nomatim API地理编码得到的。
这是我如何得到每个村庄的经度和纬度的一个例子:
# 获取北格林希尔的经纬度
address = 'North Greenhills, Metro Manila'
geolocator = Nominatim(user_agent="gh_agent")
location = geolocator.geocode(address)
latitude = location.latitude
longitude = location.longitude
print(latitude, longitude)
然后我把这个CSV文件上传到笔记本上。CSV文件中包含的数据是每个村庄的名称、经度和纬度。
以下是我在这个项目中使用的库:
import requests # 处理请求
import pandas as pd # 数据分析
import numpy as np # 以矢量化的方式处理数据
import json # 将json文件解析为Python字典或列表
import matplotlib.cm as cm # 绘制点
import matplotlib.colors as colors # 绘制点
from pandas.io.json import json_normalize # 将json文件转换为pandas数据框
!python3 -m pip install folium
import folium # 创建地图
from geopy.geocoders import Nominatim # 对需要的不同区域的经度和纬度进行地理编码
from sklearn.cluster import KMeans # 用于创建k-means模型
print('Libraries imported.')
导入这些库之后,我还定义了我的Foursquare API凭据(https://developer.foursquare.com/),因为Foursquare API会请求村庄附近不同菜市场的名称和评分。
#Foursquare credentials (hidden cell)
# @hidden_cell
CLIENT_ID = '[insert Foursquare ID here]'
CLIENT_SECRET = '[insert Foursquare Secret here]'
ACCESS_TOKEN = '[insert Access Token here]'
VERSION = '20180605' # Foursquare API version
LIMIT = 100 # A default Foursquare API limit value
然后我把这个村庄位置数据的CSV文件作为pandas数据帧上传到笔记本里,命名为“df_villages”。
我做了一些探索性的数据分析,用Folium在地图上可视化村庄。我制作了一张马尼拉周围的地图,把村庄标成蓝点。
# 获取马尼拉的纬度和经度
address = 'Metro Manila'
geolocator = Nominatim(user_agent="mm_agent")
location = geolocator.geocode(address)
latitude = location.latitude
longitude = location.longitude
# 生成大马尼拉的村庄地图
village_map = folium.Map(location=[latitude, longitude], zoom_start=13)
# 将村庄设置为蓝色圆圈标记
for Latitude, Longitude, label in zip(df_villages.Latitude, df_villages.Longitude, df_villages.Village):
folium.CircleMarker(
[Latitude, Longitude],
radius=5,
color='blue',
popup=label,
fill = True,
fill_color='blue',
fill_opacity=0.6
).add_to(village_map)
# 显示地图
village_map
乍一看,我发现大部分村庄都位于大马尼拉的中心,在北部和南部也有一些例外。
它看起来有4个可能的村庄簇,但因为一周有6个工作日需要送货,我想把所有这些村庄分成6个簇。
因为运送的货物容易变质,而且很容易变质,所以只有相邻村庄的居民才应该在一天内送达。
使用k-means聚类算法对未标记数据基于它们彼此的接近度进行聚类
#获取k-means 大马尼拉村庄簇
#6个簇,每个簇对应一个工作日
kclusters = 6
kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(df_villages[["Latitude", "Longitude"]])
“kclusters = 6”部分表示将创建6个簇。数据集被分成6簇后,一个新的列被添加到数据帧中用于簇标签。
# 为数据帧添加簇标签
df_villages.insert(0, 'Cluster Labels', kmeans.labels_)
# 显示数据帧的前5行
df_villages.head()
为了可视化簇,创建了一个名为“cluster_map”的新地图,其中每个簇标签都被分配了特定的颜色,并使用folium在地图上绘制。
# 地图簇
cluster_map = folium.Map(location=[latitude, longitude], zoom_start=12)
# 设置簇的颜色
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]
centers = kmeans.cluster_centers_
# 设置markers
cluster_markers = []
for lat, lon, village, cluster in zip(df_villages['Latitude'], df_villages['Longitude'], df_villages['Village'], df_villages['Cluster Labels']):
label = folium.Popup(str(village) + ' 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=0.7).add_to(cluster_map)
cluster_map
3.结果和分析
既然已清楚哪些村庄将在同一天送达,就必须为每一簇分配一个特定的菜市场,以便在送达期间尽量减少货物所需要时间。
重要的是,选择的菜市场要尽可能接近簇,特别是如果有顾客点海鲜或其他容易变质的产品。
奎松市库堡的农贸市场是大马尼拉地区较为高端的生鲜市场之一,这些专属村庄的居民经常在这里购买新鲜农产品。
由于这个原因,我不再在0和4簇区域周围寻找菜市场,因为农贸市场位于这些区域之间。
我建议这些菜市场供应商,因为1)它已经有很好的声誉;2)更低的价格。
由于目标市场是为两个簇的居民在农贸市场的同一档位购买商品,所以价格亦会减至最低。
我只为第1、2、3和5簇搜索最好的菜市场。
因为簇1只有两个村庄(Ayala Alabang和Ayala Southvale),所以我选择了一个作为在簇1附近寻找菜市场的参考,这将是第一个被送达的村庄。
我得到了阿亚拉阿拉邦的纬度和经度,然后通过Foursquare API搜索阿亚拉阿拉邦附近的“菜市场”。
# 获取Ayala Alabang村的经纬度
address_1 = 'Ayala Alabang, Metro Manila'
geolocator_1 = Nominatim(user_agent="1_agent")
location_1 = geolocator_1.geocode(address_1)
latitude_1 = location_1.latitude
longitude_1 = location_1.longitude
# 搜索每个选定的地址附近的菜市场
search_query = 'wet market'
radius = 2000
print(search_query)
# 获取簇1附近菜市场的结果
url_1 = 'https://api.foursquare.com/v2/venues/search?client_id={}&client_secret={}&ll={},{}&oauth_token={}&v={}&query={}&radius={}&limit={}'.format(CLIENT_ID, CLIENT_SECRET, latitude_1, longitude_1,ACCESS_TOKEN, VERSION, search_query, radius, LIMIT)
# 获取簇1附近菜市场的结果
results_1 = requests.get(url_1).json()
# 将JSON的相关部分分配给场馆
venues_1 = results_1['response']['venues']
# 将场馆转换为数据帧
df_results_1 = json_normalize(venues_1)
df_results_1
现在在簇1附近有一个菜市场列表,我清理了数据,以便更容易理解。我保存了清理数据为“df_markets_1”
# 只保留包括场地名称和任何与地点相关的列
filtered_columns_1 = ['name', 'categories'] + [col for col in df_results_1.columns if col.startswith('location.')] + ['id']
df_markets_1 = df_results_1.loc[:, filtered_columns_1]
# 提取场地类别的函数
def get_category_type(row):
try:
categories_list = row['categories']
except:
categories_list = row['venue.categories']
if len(categories_list) == 0:
return None
else:
return categories_list[0]['name']
# 为每一行筛选类别
df_markets_1['categories'] = df_markets_1.apply(get_category_type, axis=1)
# 清理列名,只保留最后一个项
df_markets_1.columns = [column.split('.')[-1] for column in df_markets_1.columns]
df_markets_1
我可以看到,并非所有的搜索结果都是菜市场,比如“Filinvest Corporate City”,它被标记为一个社区。但因为这里大部分都是菜市场,所以我在地图上想象出这些点,并找出离阿亚拉阿拉邦最近的市场。
# 将菜市场用黄色的圆圈标记在地图上
for lat, lng, label in zip(df_markets_1.lat, df_markets_1.lng, df_markets_1.name):
folium.CircleMarker(
[lat, lng],
radius=5,
color='yellow',
popup=label,
fill = True,
fill_color='yellow',
fill_opacity=0.6
).add_to(cluster_map)
# 显示地图
cluster_map
根据地图,离阿亚拉阿拉邦最近的市场是大学大道上的周六市场。
我查看了大学大道上周六市场的评分看看这个市场是否符合潜在客户的标准,也就是村里的独家居民。
# 查看大学街周六市场的评分。
venue_id_SMUA = '4b9c7413f964a520d96936e3' # Saturday Market on University Ave.
url_SMUA = 'https://api.foursquare.com/v2/venues/{}?client_id={}&client_secret={}&oauth_token={}&v={}'.format(venue_id_SMUA, CLIENT_ID, CLIENT_SECRET,ACCESS_TOKEN, VERSION)
result_SMUA = requests.get(url_SMUA).json()
try:
print(result_SMUA['response']['venue']['rating'])
except:
print('This venue has not been rated yet.')
因为这个地方还没有评分,我试着得到附近菜市场的评分。然而,所有这些都没有评分,所以我试着看能否拍下场地的照片,判断它是否有序,产品质量是否良好。
url_SMUA_photo = 'https://api.foursquare.com/v2/photos/{}?client_id={}&client_secret={}&oauth_token={}&v={}'.format(venue_id_SMUA, CLIENT_ID, CLIENT_SECRET,ACCESS_TOKEN, VERSION)
result_SMUA_photo = requests.get(url_SMUA_photo).json()
result_SMUA_photo
Foursquare API上也没有照片,所以在谷歌上进行了外部图片搜索。照片显示,这个市场看起来很干净,似乎迎合了合适的顾客。
因此,我建议目标市场为周六市场供应商作为Cluster 1的供应商。
在选择簇2、3和5的市场供应商时,我重复了同样的过程。
因为簇2只包含3个村庄,所以我选择了一个不是中间村庄的村庄作为在簇2附近寻找菜市场的参考(Serendra one),这将是第一个被送达的村庄。
在为Serendra One附近的菜市场创建了一个名为“df_markets_2”的新数据帧之后,我将这些数据帧绘制在了“cluster_map”上。
正如地图上所示,在簇2附近有许多市场,因为它就在一个叫做“Market! Market!“!这可能是使用Foursquare API的限制之一,因为即使是商场里的非食品店也会出现在搜索结果中,因为它们的名字里有“Market”这个词。
但在查看了附近的点后,发现商场里还有一个农贸市场!,因此它可能是簇2的潜在供应商。
我查了市场评分,但是没有。我决定看看它是否能从Foursquare用户那里得到一些提示,让他们知道它是怎样的。
## MMFM 技巧
limit_MMFM = 15 # set limit to be greater than or equal to the total number of tips
tips_MMFM = 'https://api.foursquare.com/v2/venues/{}/tips?client_id={}&client_secret={}&oauth_token={}&v={}&limit={}'.format(venue_id_MMFM, CLIENT_ID, CLIENT_SECRET,ACCESS_TOKEN, VERSION, limit_MMFM)
results_MMFM = requests.get(tips_MMFM).json()
results_MMFM
Foursquare API上也没有提示。由于这个菜市场和最近的菜市场(已经离簇有点远了)也没有评分,所以我决定推荐Market! Market! Farmers Market为簇2的供应商。
从照片上看,它看起来像一个干净而信誉良好的菜市场,尤其是因为它位于一个受欢迎的购物中心的区域。因此,我推荐它。
接下来,我寻找簇3最好的菜市场供应商。
因为第3簇只包括3个村庄,所以我选择了一个不是中间村庄的村庄作为在第3组附近寻找菜市场的参考,这将是第一个被送达的村庄。
我特意选择了La Vista Village作为参考,因为另外两个村庄离得比较近,而且在河的一边,所以从物流上来说,先送货到La Vista比较容易。
邻近的菜市场再次被放置在数据帧“df_markets_3”中,并绘制在“clusters_map”上。
根据地图,离拉维斯塔最近的市场是Viaga公共市场。因为这个地方在Foursquare上没有评分、提示或照片,我就去最近的菜市场看看他们是否有评分、提示或照片。
不幸的是,他们都不知道。我试着在谷歌图片中寻找Viaga公共市场、Tumana公共市场和taban公共市场(离La Vista最近的菜市场)的照片,但也没有这些图片。
我想在大马尼拉(甚至在菲律宾)还没有太多评论、提示和照片的场所。或者没有太多的人在这个地区的菜市场上评论和留下提示。
在这种情况下,我将让这份报告的用户自己去探索建议的生鲜市场,并判断这些市场是否适合他们的客户。
最后,我对Cluster 5重复了这个过程。
因为Urdaneta村在簇5的中心,所以我用它作为参考来搜索离Cluster最近的菜市场列表。这是簇5附近的生鲜市场地图:
在第5簇的地图上,我注意到很多点实际上不是菜市场,有些是夜市或超市。如果农贸市场供应商至少靠近簇边界上的一个村庄,比如簇右上角的洛克威尔·马卡蒂(Rockwell Makati),可能是最好的。
这样,市场供应商就可以靠近该簇的送货路线的“入口点”。罗克韦尔马卡蒂(Rockwell Makati)附近最近的地方是高端超市“Rustans市场”(Marketplace by Rustans)和高端食品广场“网格食品市场”(The Grid Food Market)。
由于这两个都不是真正的生鲜市场,我推荐Poblacion公共市场作为这个簇的生鲜市场供应商,因为它是离罗克韦尔马卡蒂最近的地方。
再次,由于这个地点没有评分,提示,或可用的照片,我基于这个市场的质量再次谷歌图像。
我在谷歌上只看到一个市场的图片,内部和农贸市场很像,所以我猜这个菜市场的产品质量应该也差不多。因此,我推荐Poblacion Public Market作为簇5的生鲜市场供应商。
4.结论
大马尼拉的45个专属村庄根据彼此的邻近程度被分成6个送货组(0-5组)。
使用Foursquare API和一些额外的人类知识(如了解农贸市场的口碑和目标客户),我能够识别一些推荐的生鲜市场,目标市场可以购买供应品,出售给这些专属村庄的居民。这些菜市场包括:
我也通过这个项目得出结论,虽然这种数据和技术可以帮助商业决策,但是大部分的分析仍然依赖于人类的经验和直觉。
总而言之,技术是一种让决策变得更容易的工具,但它需要通过现实世界的人类知识集成来优化。
该报告是作为我的IBM数据科学专业证书的capstone项目的一部分而编写的。你可以在我的笔记本上查看完整的代码:https://dataplatform.cloud.ibm.com/analytics/notebooks/v2/86bb2e47-499b-400a-bb5d-3f4a2efcc52a/view?access_token=9f33999c9b47a3049f9759f69f3c1853b8a417a5ac52053302612ce2cc8e6