文章目录
- 项目背景
- 1 引言
- 2 数据说明
- 一、数据导入及预处理
- 1 数据导入
- 2 数据观察
- 2.1 查看数据形状
- 2.2 检查缺失值
- 2.3 有无重复值
- 3 数据预处理
- 3.1 获取详细地址
- 3.2 批量获取经纬度
- 3.2.1 安装geopy包
- 3.2.2 批量获取经纬度
- 二、优衣库门店可视化
- 1 数据获取
- 1.1 读取地点数据
- 1.2 筛选优衣库门店
- 2 门店可视化
- 2.1 带涟漪效果的散点图
- 2.2 热力图
- 三、拜访数据分组
- 1 数据获取
- 2 拜访数据分组
- 2.1 KMeans分组
- 2.2 输出分组结果
- 2.2.1 分组结果
- 2.2.2 中心点坐标
- 3 K值调整
- 3.1 The Elbow Method
- 3.2 分组结果可视化
- 3.3 聚类中心地址
- 4 大屏展示
- 总结与展望
- 1 项目总结
- 2 后期展望
项目背景
1 引言
在正文开始之前,大家可以先来考虑这样两个问题:
-
一家连锁企业在开设线下门店时会以哪些因素为参考呢?
-
在不同层次的城市应该分别开设多少家门店,门店位置如何选取,才能促其利润最大化呢?
这其实就是一个数据分析的过程,如果我们拥有相应城市的人口、GDP、人群分布、用户行为等数据,我们就可以使用尽可能科学的方法帮助我们计算出相应的商业中心。
那么,什么才是最科学的方法呢?下面就让我们一起来探索吧。
2 数据说明
该数据集是一份统计自在上海市热门地点活动的80人的拜访数据样例,包含顾客姓名、剩余拜访次数、地理位置等信息。
分为以下三个csv文件:
-
uniqlo.csv
-
uniqlo1.csv
-
uniqlo2.csv
其中uniqlo2.csv是在uniqlo.csv的基础上添加了详细地址、经纬度等信息,具体方法分两步:
-
使用百度地图,根据地址信息查询其对应的详细地址;
-
使用经纬度查询工具,根据详细地址匹配对应位置的经纬度。
为了说明地名数据的一般处理方法,下面我们使用不含详细地址及经纬度数据的这一份csv文件进行分析;大家也大家根据自己的情况考虑略过这部分操作,直接使用最后一份数据集进行后续的分析。
一、数据导入及预处理
1 数据导入
#读取不含经纬度信息的拜访数据
import pandas as pd
data = pd.read_csv('./uniqlo.csv')
data.head()
2 数据观察
2.1 查看数据形状
#查看数据形状
data.shape
2.2 检查缺失值
#检查各列有无缺失值
data.isnull().sum()
2.3 有无重复值
#检查各列独立元素的数目
print('姓名数目为:',data['姓名'].nunique())
print('剩余拜访次数枚举值为:',data['剩余拜访次数'].unique())
print('地址数目为:',data['地址'].nunique())
姓名数目为: 80 剩余拜访次数枚举值为: [1 2 3] 地址数目为: 73
3 数据预处理
我们可以使用百度地图查询数据集中地址所对应的详细地址,继而使用Python支持的geopy工具包完成详细地址到经纬度的转换。
3.1 获取详细地址
由于数据量不大,我们此处使用百度地图手动处理;后续可以考虑调用适合的API来完成这一转换。
#读取含详细地址的数据
data = pd.read_csv('./uniqlo1.csv')
data.head()
3.2 批量获取经纬度
3.2.1 安装geopy包
####
#安装geopy包,并指定清华源下载
!pip install geopy -i https://pypi.tuna.tsinghua.edu.cn/simple/
print('-'*60)
print('geopy包安装完成!')
3.2.2 批量获取经纬度
# #根据详细地址获取经纬度
# # 速度较慢,可直接读取经纬度解析完成的数据uniqlo2.csv
# import warnings
# warnings.filterwarnings('ignore')
# from geopy.geocoders import Nominatim
# geolocator = Nominatim(user_agent='Mozilla/5.0(Windows NT 10.0;WOW64)ApplewebKit/537.36(KHTML,like Gecko)Chrome/55.0.2883.75 Safari/537.36')
# from geopy.extra.rate_limiter import RateLimiter
# geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
# #获取location
# data['location'] = data['详细地址'].apply(geocode)
# #获取经度
# data['经度'] = data['location'].apply(lambda loc: loc.longitude if loc else None)
# #获取纬度
# data['纬度'] = data['location'].apply(lambda loc: loc.latitude if loc else None)
# print('经纬度解析完成!')
# data.head()
二、优衣库门店可视化
1 数据获取
前面我们已经得到了带经纬度信息的优衣库门店数据,由于该数据中包含一些其他地点,因此我们需要对其进行相应的筛选;考虑到前一步得到的经纬度数据只到路道级别,不能保持原有数据的精确度,因此我们转而使用经经纬度解析工具解析后的这一份地点数据进行如下操作。
1.1 读取地点数据
#读取含经纬度的地点数据
data2 = pd.read_csv('./uniqlo2.csv')
data2.head()
1.2 筛选优衣库门店
#根据地址字段筛选出含'优衣库'的数据行
uniqlo = data2[data2['地址'].str.contains('优衣库')]
uniqlo.head()
#一共筛选出了多少条数据
uniqlo.shape
#去重后的门店数
uniqlo['地址'].nunique()
#40个门店分别是哪些
uniqlo['地址'].unique()
#获取40个门店的名称及经纬度数据
import numpy as np
uniqlo = uniqlo.groupby('地址',as_index=False).agg({'剩余拜访次数':sum,'经度':np.mean,'纬度':np.mean})
uniqlo.head()
2 门店可视化
需安装包:
pip install pyecharts -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install echarts-countries-pypkg -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install echarts-china-provinces-pypkg -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install echarts-china-cities-pypkg -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install echarts-china-counties-pypkg -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install echarts-china-misc-pypkg -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install echarts-united-kingdom-pypkg -i https://pypi.tuna.tsinghua.edu.cn/simple/
2.1 带涟漪效果的散点图
#使用pyecharts中的Geo类绘制散点图
from pyecharts.charts import Geo
from pyecharts import options as opts
from pyecharts.globals import GeoType
city = '上海'
#实例化一个Geo类
geo = Geo()
#以上海市地图为底景
geo.add_schema(maptype=city)
# #添加地点坐标至坐标库中
for i in range(40):
geo.add_coordinate(uniqlo.iloc[i]['地址'],uniqlo.iloc[i]['经度'],uniqlo.iloc[i]['纬度'])
data_pair = [(uniqlo.iloc[i]['地址'],int(uniqlo.iloc[i]['剩余拜访次数'])) for i in range(40)]
# 将数据添加到地图上
geo.add('',data_pair,type_=GeoType.EFFECT_SCATTER, symbol_size=9)
# 设置样式
geo.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
#自定义分级
pieces = [
{'min': 0, 'max': 1, 'label': '1', 'color': '#50A3BA'},
{'min': 1, 'max': 2, 'label': '2', 'color': '#DD675E'},
{'min': 2, 'max': 3, 'label': '3', 'color': '#E2C568'},
{'min': 3, 'label': '4', 'color': '#3700A4'}
]
#是否自定义分段
geo.set_global_opts(
visualmap_opts=opts.VisualMapOpts(is_piecewise=True, pieces=pieces),
title_opts=opts.TitleOpts(title='上海市优衣库门店可视化'),
)
geo.render_notebook()
2.2 热力图
绘制热力图只需将GeoType参数为设置HEATMAP即可,具体展示如下:
city = '上海'
#实例化一个Geo类
geo2 = Geo()
#以上海市地图为底景
geo2.add_schema(maptype=city)
# #添加地点坐标至坐标库中
for i in range(40):
geo2.add_coordinate(uniqlo.iloc[i]['地址'],uniqlo.iloc[i]['经度'],uniqlo.iloc[i]['纬度'])
data_pair = [(uniqlo.iloc[i]['地址'],int(uniqlo.iloc[i]['剩余拜访次数'])) for i in range(40)]
# 将数据添加到地图上
geo2.add('',data_pair,type_=GeoType.HEATMAP, symbol_size=5)
# 设置样式
geo2.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
#自定义分级
pieces = [
{'min': 0, 'max': 1, 'label': '1', 'color': '#50A3BA'},
{'min': 1, 'max': 2, 'label': '2', 'color': '#E2C568'},
{'min': 2, 'max': 3, 'label': '3', 'color': '#DD675E'},
{'min': 3, 'label': '4', 'color': '#DD0200'}
]
#是否自定义分段
geo2.set_global_opts(
visualmap_opts=opts.VisualMapOpts(is_piecewise=True, pieces=pieces),
title_opts=opts.TitleOpts(title='上海市优衣库门店可视化'),
)
geo2.render_notebook()
三、拜访数据分组
由于我们事先不知道将要把数据点分为哪些组,所以这是一个典型的无监督学习问题,以下我们将采用最常用的KMeans算法对拜访数据进行分组。
1 数据获取
#拜访地点数据获取
visit = data2[['地址','经度','纬度']]
visit.head()
2 拜访数据分组
2.1 KMeans分组
这里简要介绍一下KMeans算法的步骤:
-
随机初始化k个聚类中心;
-
将n个待分类数据分配到距离它最近的聚类中心;
-
根据步骤2结果,重新计算新的聚类中心;
-
若达到最大迭代步数或两次迭代差小于设定的阈值则算法结束,否则重复步骤2。
#使用KMeans算法将拜访数据分为8组
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=8,
init='k-means++',)
X = visit[['经度','纬度']]
y = [0,1,2,3,4,5,6,7]
kmeans.fit(X,y)
y_pred = kmeans.predict(X)
y_pred
2.2 输出分组结果
2.2.1 分组结果
#将各点分组结果添加到数据中
visit_result = visit
visit_result['分组'] = y_pred
visit_result.head()
2.2.2 中心点坐标
#中心点坐标
center = kmeans.cluster_centers_
center
#获取中心点详细地址
import time
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent='Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36 SE 2.X MetaSr 1.0',
timeout=5)
# 注意:建议访问速度不要太快,否则报timeout错误
# for x,y in center:
# print(x,y)
# location = geolocator.reverse((y,x))
# print(location.address)
# time.sleep(1.5)
3 K值调整
3.1 The Elbow Method
由于这里没有事先对数据分成几组作出规定,因此我们有必要对K值进行调整,来找到一个能够使我们比较满意的分组结果;事实上,KMeans算法中K值的选取一直是业界比较关心的一个问题,这里我们采用最常用的 The Elbow Method(“肘方法”) 来选取一个恰当的K值。
#对不同的K值分别计算误差和
from sklearn.cluster import KMeans
inertia = []
for i in range(1,21):
kmeans = KMeans(n_clusters=i,
init='k-means++')
X = visit[['经度','纬度']]
kmeans.fit(X)
inertia.append(kmeans.inertia_)
inertia
#绘制误差和随K值的变化曲线图
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
plt.figure(figsize=(12,6))
plt.plot(range(1,11),inertia[0:10])
plt.title('The Elbow Method')
plt.xlabel('K')
plt.ylabel('inertia')
plt.show()
这里可以看到,当K<=10时,K=3是一个比较好的分组数目;为了解误差后续的变化趋势,我们看下K<=20时的结果。
#绘制误差和随K值的变化曲线图
plt.figure(figsize=(12,6))
plt.plot(range(1,21),inertia)
plt.title('The Elbow Method')
plt.xlabel('K')
plt.ylabel('inertia')
plt.show()
可以看到,K<=20时,拐点出现在K=9附近,但此处误差下降的速度并不明显,下面我们按照K=3来进行分组。
3.2 分组结果可视化
#使用KMeans算法将拜访数据分为3组
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=3,
init='k-means++',)
X = visit[['经度','纬度']]
kmeans.fit(X)
y_pred = kmeans.predict(X)
y_pred
#K=3时拜访数据分组可视化
from pyecharts.charts import Geo
from pyecharts import options as opts
from pyecharts.globals import GeoType
city = '上海'
#实例化一个Geo类
geo3 = Geo()
#以上海市地图为底景
geo3.add_schema(maptype=city)
# #添加地点坐标至坐标库中
for i in range(80):
geo3.add_coordinate(visit.iloc[i]['地址'],visit.iloc[i]['经度'],visit.iloc[i]['纬度'])
data_pair = [(visit.iloc[i]['地址'],int(y_pred[i])) for i in range(80)]
# 将数据添加到地图上
geo3.add('',data_pair,type_=GeoType.EFFECT_SCATTER, symbol_size=9)
# 设置样式
geo3.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
#自定义分级
pieces = [
{'min': 0, 'max': 0, 'label': '1', 'color': '#50A3BA'},
{'min': 0, 'max': 1, 'label': '2', 'color': '#DD0200'},
{'min': 1, 'max': 2, 'label': '3', 'color': '#E2C568'}
]
#是否自定义分段
geo3.set_global_opts(
visualmap_opts=opts.VisualMapOpts(is_piecewise=True, pieces=pieces),
title_opts=opts.TitleOpts(title='上海市热门地点分组可视化'),
)
geo3.render_notebook()
3.3 聚类中心地址
#获取聚类中心坐标
center_ = kmeans.cluster_centers_
center_
# 可以到 https://lbs.amap.com/tools/picker 输入经纬度121.48337518,31.23489423查看地图位置
#获取聚类中心点详细地址
# from geopy.geocoders import Nominatim
# geolocator = Nominatim(user_agent='Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36 SE 2.X MetaSr 1.0',
# timeout=5)
# for i in range(3):
# location = geolocator.reverse((center_[i][1],center_[i][0]))
# print(location.address)
# time.sleep(1.5)
4 大屏展示
from pyecharts.charts import Page
page = Page(layout=Page.DraggablePageLayout, page_title="大屏展示")
page.add(
geo,
geo2,
geo3)
# 先保存到test.html 然后打开,拖拽图片自定义布局, 之后记得点击左上角“save config”对布局文件进行保存。
# 会生成一个chart_config.json的文件,这其中包含了每个图表ID对应的布局位置
page.render('test.html')
# 然后运行下面这行代码。保存布局好的的仪表盘文件。
page.save_resize_html('test.html', cfg_file='chart_config.json', dest='大屏展示1.html')
总结与展望
1 项目总结
本文展示了利用较少的数据量(主要是地点名称数据)也可以进行可视化分析与数据分组 等操作,其中包含了地点名称数据向地点详细地址、经纬度等信息的转换,以及根据经纬度数据对地点的聚类分组。
其中难点 便在于地点名称数据向地点详细地址、经纬度数据的转换,这个过程中我们可能要调用适当的API来完成这一转换,本文中我们使用的是geopy工具包中的地理位置编码与反编码等方法来实现。在实际使用过程中,我们发现geopy包在对地理位置进行编码时对地点名称数据是有一定要求的,如“优衣库南京西路店”不能直接进行编码,于是我们统一使用“上海市(XX区)XX路”进行编码。
而由模糊地址向详细地址转换这一步暂时没有找到合适的API,这也是后期我们考虑优化的一个方向。
以上这种数据分组常见于寻找点群对应的城市中心,为门店选址提供依据等。
2 后期展望
下面我们来看这样一个实际场景:
- 知乎某运营部门拟定对居住在北京,且粉丝数10w的大V举行一次线下交流会,考虑到北京全市面积较大且交通不便,决定将此次聚会分散至几个不同的地点进行,假设每个聚会地点人均消费相同,且已有每个人居住地点的数据,那么我们应当如何选取聚会地点,才能使每个人距离聚会地点尽可能近且每个聚会地人数尽可能多呢?
这就是一个典型的无监督数据分组问题,我们可以考虑使用上述的Kmeans算法来进行聚类。
由于本文所使用的数据集较少,其中所包含的优衣库门店并非全量数据,所以项目整体更偏向于一种分析思路的阐明,如果我们拥有比较多的数据,预期将会得到一个比较好的分组结果;
另外Kmeans算法本身存在一些如对异常值较为敏感、数据量大时算法比较复杂等缺点,因此我们也可以考虑使用基于密度的聚类方法对数据来进行分组;
K值的确定:以上即使我们使用了“肘方法”,但由于误差的下降较为平缓,因此选取的K值比较难界定是否最优,实际情形中我们还需要根据具体情况进行处理;
在计算空间两点距离时我们直接使用了最常见的欧式距离,但这种近似仅适用于经纬度相差不大的点群,当点群之间经纬度相差较大时,我们应该转而使用球面距离。