最近老师布置了一个作业,内容是对国内各省份生产总值与固定资产投资的数据,采用最短距离法进行聚类。原本这种关于矩阵运算的问题用Matlab是比较合适的,奈何Matlab我运用的不是太熟练,所以选择采用python完成。写这篇博客的目的是记录一下代码中使用到的关于Numpy库的一些函数,以及整理一下实现的流程以供需要完成类似功能的小伙伴参考。
读取数据部分思路即将数据逐行读入,设置一个标识以不存入第一行数据,最后根据逗号分割每行的内容并将其分别存入对应的列表中。直接附上源码吧!
def getData(data_file):
x_data = []
y_data = []
global name
name = []
iden = 1
f = open(data_file, "r", encoding='utf_8')
line = f.readline()
while line != '':
# 去除第一行
if iden == 1:
iden = iden + 1
line = f.readline()
continue
# 存入数据至两个列表
tem = line.strip('\n').split(',')
name.append(tem[0])
x_data.append(tem[1])
y_data.append(tem[2])
line = f.readline()
return list(map(float, x_data)), list(map(float, y_data))
利用np.max函数将数据进行极大值标准化,
# 极大值标准化
x_max = np.max(x_data)
y_max = np.max(y_data)
x = x_data / x_max
y = y_data / y_max
maum_value = np.column_stack((x, y))
由最短距离聚类法的的实现过程可知,需要一个距离矩阵,此处采用较为简单的绝对值距离,公式与代码如下。
# 求数据的绝对值距离矩阵
abs_matrix = np.zeros((len(x_data), len(y_data)))
for i in range(len(x_data)):
for j in range(len(y_data)):
if i > j:
tem = 0
for k in range(np.shape(maum_value)[1]):
tem = tem + np.fabs(maum_value[i][k] - maum_value[j][k])
abs_matrix[i][j] = tem
return abs_matrix
由于数据预处理得到的结果是一个下三角矩阵,若直接使用np.min找最小值会将0值找出,所以此处我采用较为直接的办法,直接将矩阵的0所由0值变为100。如果有更好的办法,欢迎交流。
# 寻找矩阵中最小的值的索引
abs_matrix[(abs_matrix == 0)] = 100
min_arg1 = np.argwhere(abs_matrix == np.min(abs_matrix))[0][0]
min_arg2 = np.argwhere(abs_matrix == np.min(abs_matrix))[0][1]
因为绝对值矩阵的右上边全为100,导致寻找新类与各类矩阵的距离代码实现较为繁琐,所以此处我将下三角矩阵转为对称矩阵。而后只需取出两行在判断每列的最小值(两行的索引就是前面最小值所在的行与列)。最后删除要聚类的两个数据即为新类到各类的距离。
# 将下三角矩阵变为对称矩阵
abs_matrix[(abs_matrix == 100)] = 0
tem_matrix = np.where(abs_matrix, abs_matrix, abs_matrix.T)
# 获取选出的两行比较最小值,赋值到新建的行
tem_matrix = tem_matrix[[min_arg1, min_arg2], :]
new_list = np.min(tem_matrix, axis=0)
# 删除要聚类的两个数据,并加上一个自己到自己的距离0
new_list = np.delete(new_list, [min_arg1, min_arg2], axis=0)
new_list = np.append(new_list, 0)
由于最后想知道聚类的过程,所以此处我选择将各类的名字加入绝对值矩阵,以便打印信息。
# 将名字加入矩阵中,以便后期知道哪个和哪个聚类
abs_matrix = np.column_stack((name, abs_matrix))
# 向name的第一个位置补一个0
name.insert(0, '0')
abs_matrix = np.row_stack((name, abs_matrix))
# 取出聚类的信息,以便打印名字
name_1 = abs_matrix[min_arg1 + 1, 0]
name_2 = abs_matrix[min_arg2 + 1, 0]
print(f"{name_1}与{name_2}聚为一类,记为{name_1},{name_2}")
根据最短距离聚类法的思想,需要将参与聚类的类从原矩阵中删除。
# 删除对应的行和列
abs_matrix = np.delete(abs_matrix, [min_arg1 + 1, min_arg2 + 1], axis=0)
abs_matrix = np.delete(abs_matrix, [min_arg1 + 1, min_arg2 + 1], axis=1)
由于需要计算矩阵的最小值,所以需要将前面加入的名字去除,并将矩阵由字符型转换为浮点型。
# 去除名字并变为浮点型矩阵
abs_matrix = np.delete(abs_matrix, 0, axis=0)
abs_matrix = np.delete(abs_matrix, 0, axis=1)
abs_matrix = abs_matrix.astype("float32")
此处需要将前面得到的新类到各类的距离矩阵中加入一个0,因为自己到自己的距离为0。
# 创建一个加入新类后的矩阵
one_list = np.zeros((np.shape(abs_matrix)[0], 1))
abs_matrix = np.column_stack((abs_matrix, one_list))
abs_matrix = np.row_stack((abs_matrix, new_list))
由于代码中需要将名字加入距离矩阵,所以需要删除名字列表中已经聚类的名字,并且需要加入新类的名称。
# 处理名字列表
name.pop(0)
name.remove(name_1)
name.remove(name_2)
new_name = name_1 + "," + name_2
name.append(new_name)
至此算法主要逻辑已经完成,最后只需要根据某个条件重复上述过程即可。很显然,结束条件即最终的距离矩阵大小为1*1时,标志着聚类完成。所以此处我的思路为设置一个标识另其初始值为0,当聚类完成时值变为1。
while biaoshi == 0:
min_cluster(abs_matrix)
由于我对于算法之类的基础知识比较薄弱,所以代码逻辑的思路较为简单。如果读者有更好的思路或者我有什么错误的地方,欢迎交流指正!