CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
用 Python 访问 Baidu Web 的 API,先用 Baidu Web 的 API 获得数据,然后用 kMeans 算法对地理位置进行聚类,并对聚类得到的簇进行后处理。
实验目的
利用 python 实现 kMeans 算法
实验环境
硬件
所用机器型号为 VAIO Z Flip 2016
- Intel(R) Core(TM) i7-6567U CPU @3.30GHZ 3.31GHz
- 8.00GB RAM
软件
- Windows 10, 64-bit (Build 17763) 10.0.17763
- Visual Studio Code 1.39.2
- Python 2019.10.41019:九月底发布的 VSCode Python 插件支持在编辑器窗口内原生运行 juyter nootbook 了,非常赞!
- Remote - WSL 0.39.9:配合 WSL,在 Windows 上获得 Linux 接近原生环境的体验。
- Windows Subsystem for Linux [Ubuntu 18.04.2 LTS]:WSL 是以软件的形式运行在 Windows 下的 Linux 子系统,是近些年微软推出来的新工具,可以在 Windows 系统上原生运行 Linux。
- Python 3.7.4 64-bit (‘anaconda3’:virtualenv):安装在 WSL 中。
获取地图数据
百度地图提供 API 的网址:http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-placeapi
注册成为开发者
注册登录百度账号之后,点击获取秘钥,弹出注册成为开发者页面。
点击创建应用:注意:IP 白名单中要填入访问方的公网 IP,查看 ip 白名单中的 ip 是否与本机 ip 一致,若不一致则改成本机 ip。
应用类型选择服务端,可以看到下面有需要的 Place API v2 服务。点击确定就可以看到秘钥。
使用 API 获取数据
查看网页,了解 API 的使用格式。API 的格式:
1
http://api.map.baidu.com/place/v2/search?q=查询内容&page_size=范围记录数量&page_num=分页页码®ion=地区&output=数据格式&ak=秘钥
kMean 算法实现
建立辅助函数函数
loadDataSet()
读取文件的数据,函数distEclud()
计算两个向量的欧式距离,函数randCent()
随机生成 k 个随机质心。
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> import kmeans
>>> from numpy import*
#从文本文件中构建矩阵:
>>>dataset=mat(kmeans.loadDataSet('testSet.txt'))
#测试函数randCent():
>>>min(dataset[:,0])
>>>min(dataset[:,1])
>>>max(dataset[:,1])
>>>max(dataset[:,0])
#查看randCent()能否生成min到max之间的值:
>>>kmeans.randCent(dataset, 2)
#测试距离计算函数:
>>>kmeans.distEclud(dataset[0], dataset[1])
kMean 算法实现
函数kMeans()
接受 4 个输入参数,只有数据集及簇的数目是必选参数,而计算距离和创建初始质心的函数都是可选的。
1
2
3
4
5
>>>dataset=mat(kmeans.loadDataSet('testSet.txt'))
#查看聚类结果:
>>>myCentroids,clustAssing=kmeans.kMeans(dataset,4)
#参看迭代的次数及结果:>>>myCentroids2
>>>clustAssing
用 kMeans 算法对地图上的点聚类
餐厅是一个城市的重要组成部分,在北京城内有不少餐厅,当今地政府想要建立 4 个餐厅管理服务点,对整个北京中的餐厅进行管理,但是无法确定管理服务点要建在哪里才比较合理?假设现在给出北京地区的一些饭店所在的经纬度,具体分布如下图所示。尝试利用 kMeans 依据饭店的分布,找其各部分的中心位置。
准备数据
饭店的经纬度数据存放在 Restaurant_Data_Beijing.txt 文件中,其中每一行数据的第一列代表地点的纬度(北纬),第二列代表经度(东经)
1
2
>>>dataMat=loadDataSet('Restaurant_Data_Beijing.txt')
>>>dataMat
对地理坐标进行聚类
增加两个函数:函数distSLC()
返回地球表面两点之间的距离,函数clusterPlaces ()
将文本文件中的地点进行聚类并画出结果。
1
>>>kmeans.clusterPlaces
可以与 google map 里面的标记进行对比。不同簇的数据点用不同的形状标记,+号所标注的就是对应簇的质心。可看到地点被大致分成 3 部分。依次修改 k 值为 4、5、6,观察相应的图像输出:
1
2
3
>>>kmeans.clusterPlaces(4)
>>>kmeans.clusterPlaces(5)
>>>kmeans.clusterPlaces(6)
操作习题
先运行下面这段代码获得饭店经纬度数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def geoGrab():
import json
import urllib.request
j = 0
f = open(r'Restaurant_Data_Beijing.txt', 'w')
for j in range(0, 20):
a = 'http://api.map.baidu.com/place/v2/search?q=%E9%A5%AD%E5%BA%97&page_size=20&page_num='
b = '®ion=%E5%8C%97%E4%BA%AC&output=json&ak=Oz3v1GzmrxmtYpiEmaZaxhrWo3YS5NNr'
# 上面的汉字(百分号部分)做了urlencode处理,原本是」饭店」和」北京」
# 密钥需要自己申请,然后替换掉上面的「秘钥」
c = str(j)
url = a+c+b
j = j+1
# url='http://api.map.baidu.com/place/v2/search?q=%E9%A5%AD%E5%BA%97&page_size=20'+
# '&page_num=19®ion=%E5%8C%97%E4%BA%AC&output=json&ak=qUPyb0ZPGmT41cL9L5irQzcnc48yIEck'
temp = urllib.request.urlopen(url)
# 把字符串解析成为Python对象
hjson = json.loads(temp.read().decode('utf-8'))
i = 0
for i in range(0, 20):
lat = hjson['results'][i]['location']['lat']
lng = hjson['results'][i]['location']['lng']
print('%s\t%f\t' % (lat, lng))
f.write('%s\t%f\t\n' % (lat, lng))
i = i+1
f.close()
geoGrab()
kmeans.py 中的语句「from numpy import* 」用语句「import numpy as np」代替,修改其中对应的代码,使其能够正常执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# -*-coding:utf-8-*-
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
def loadDataSet(fileName):
dataMat = []
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
# 将所有数据转换为float类型
fltLine = list(map(float, curLine))
dataMat.append(fltLine)
return dataMat
def distEclud(vecA, vecB):
return np.sqrt(sum(np.power(vecA - vecB, 2)))
def randCent(dataSet, k):
# 得到数据集的列数
n = np.shape(dataSet)[1]
# 得到一个K*N的空矩阵
centroids = np.mat(np.zeros((k, n)))
# 对于每一列
for j in range(n):
# 得到最小值
minJ = min(dataSet[:, j])
# 得到当前列的范围
rangeJ = float(max(dataSet[:, j]) - minJ)
# 在最小值和最大值之间取值
centroids[:, j] = np.mat(minJ + rangeJ * np.random.rand(k, 1))
return centroids
def distSLC(vecA, vecB):
# pi为圆周率,在导入numpy时就会导入的了
# sin(),cos()函数输出的是弧度为单位的数据
# 由于输入的经纬度是以角度为单位的,故要将其除以180再乘以pi转换为弧度
# 设所求点A ,纬度β1 ,经度α1 ;点B ,纬度β2 ,经度α2。则距离
# 距离 S=R·arc cos[cosβ1cosβ2cos(α1-α2)+sinβ1sinβ2]
a = np.sin(vecA[0, 1]*np.pi/180) * np.sin(vecB[0, 1]*np.pi/180)
b = np.cos(vecA[0, 1]*np.pi/180) * np.cos(vecB[0, 1]*np.pi/180) * \
np.cos(np.pi * (vecB[0, 0]-vecA[0, 0]) / 180)
return np.arccos(a + b)*6371.0 # 6371.0为地球半径
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
# 数据集的行数,即数据的个数
m = np.shape(dataSet)[0]
# 簇分配结果矩阵
clusterAssment = np.mat(np.zeros((m, 2)))
# 第一列储存簇索引值
# 第二列储存数据与对应质心的误差
# 先随机生成k个随机质心的集合
centroids = createCent(dataSet, k)
clusterChanged = True
# 当任意一个点的簇分配结果改变时
while clusterChanged:
clusterChanged = False
# 对数据集中的每一个数据
for i in range(m):
minDist = np.inf
minIndex = -1
# 对于每一质心
for j in range(k):
# 得到数据与质心间的距离
distJI = distMeas(centroids[j, :], dataSet[i, :])
# 更新最小值
if distJI < minDist:
minDist = distJI
minIndex = j
# 若该点的簇分配结果改变
if clusterAssment[i, 0] != minIndex:
clusterChanged = True
clusterAssment[i, :] = minIndex, minDist**2
# print centroids
# 对于每一个簇
for cent in range(k):
# 通过数组过滤得到簇中所有数据
ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0].A == cent)[0]]
# .A 方法将matrix类型元素转化为array类型
# 将质心更新为簇中所有数据的均值
centroids[cent, :] = np.mean(ptsInClust, axis=0)
# axis=0表示沿矩阵的列方向计算均值
return centroids, clusterAssment
def clusterPlaces(numClust=5):
datList = []
for line in open('Restaurant_Data_Beijing.txt').readlines():
lineArr = line.split('\t')
datList.append([float(lineArr[0]), float(lineArr[1])])
datMat = np.mat(datList)
# 进行聚类
myCentroids, clustAssing = kMeans(datMat, numClust, distMeas=distSLC)
fig = plt.figure()
# 创建一个矩形
rect = [0.1, 0.1, 0.8, 0.8]
# 用来标识簇的标记
scatterMarkers = ['s', 'o', '^', '8', 'p',
'd', 'v', 'h', '>', '<']
axprops = dict(xticks=[], yticks=[])
ax0 = fig.add_axes(rect, label='ax0', **axprops)
ax1 = fig.add_axes(rect, label='ax1', frameon=False)
for i in range(numClust):
ptsInCurrCluster = datMat[np.nonzero(clustAssing[:, 0].A == i)[0], :]
markerStyle = scatterMarkers[i % len(scatterMarkers)]
ax1.scatter(ptsInCurrCluster[:, 0].flatten(
).A[0], ptsInCurrCluster[:, 1].flatten().A[0], marker=markerStyle, s=90)
ax1.scatter(myCentroids[:, 0].flatten().A[0],
myCentroids[:, 1].flatten().A[0], marker='+', s=300)
plt.show()
if __name__ == '__main__':
clusterPlaces(6)
对聚类结果可视化(包含样本点和蔟中心,用不同颜色、记号标记)
1
2
for i in range(4,7):
clusterPlaces(i)
实验总结
通过本次实验,我大致熟悉了 Baidu Web 的 API 和用 kMeans 算法聚类的一些操作,尤其是使用百度 API,感觉非常实用。