这几天老大给了一个任务,比较有意思,也比较头疼。
Q:
现在有供应商的退货件数、送货件数、退货单数、送货单数(4个维度)。将供应商分组(k组),要求各组的个数差不多,且各组4个维度的平均值差不多。
开始想了一个算法,是根据之前做的分类算法——K-Means算法——改进的。
from _vector import squared_distance, vector_mean
import pandas as pd
class ZMeans:
def __init__(self, k):
self.k=k
self.means = None
def train(self,inputs):
# 初始点 全部设为均值
self.means=[ [inputs['退货件数'].mean(),inputs['送货件数'].mean()] for _ in range(self.k) ]
# 标记 全部设为7先
assignments = [7 for _ in range(len(inputs))]
# 简陋的设定循环次数
for x in range(1):
for i in range(len(inputs)):
assignments[i] = max(range(self.k),key=lambda j:squared_distance([inputs.ix[i,1],inputs.ix[i,2]],self.means[j]))
# 调整均值
for z in range(self.k):
i_point = [ [item1[1],item1[2]] for (idx1,item1),a in zip(inputs.iterrows() ,assignments) if a==z]
if i_point:
self.means[z]=vector_mean(i_point)
inputs['组别'] = assignments
print(inputs)
save_path =r' '
write = pd.ExcelWriter(save_path)
data.to_excel(write,sheet_name='Sheet1',header=True,index=False)
write.save()
if __name__ == '__main__':
# 读数据 数据已经按送货降序降序了
path = r' '
data = pd.read_excel(path, sheet_name='Sheet1', header=0)
# 缺失值补0
data = data.fillna(0)
data['退货件数'] = data['退货件数'].astype('float')
data['送货件数'] = data['送货件数'].astype('float')
# 分类 将dataframe传入
cluster = ZMeans(6)
cluster.train(data)
思路是参考Kmeans算法,代码是根据《数据科学入门》里面Kmeans算法的代码改写的。
①将数据按降序排好
②设定初始均值全为均值
③对每一个数据遍历,找出离这个数据最远的组别,将数据标记为该组别
④遍历dataframe,直到每行数据都标记上组
当时的思路是,Kmeans是分类算法,找最近的点即是找相似点。
而这里的分组正好相反,因为已经按顺序排好,将数据分配给最远的组别,使得全部组别在“靠近".
代码参照这个思路,可能你会有疑问。
Q:为什么初始标记全为7呢?
A:因为当时做的急,要分6个组即数字0~5分组,而初始化一个指定长度的list脑子一抽就这样写了,当然这样写不好扩展。
Q:for x in range(1):这个循环有什么?
A:一次循环就可以将所以组分配好了,但是可以再循环一次再重新分配,所以这个是想着手动控制次数用的。
刚做完输出结果的时候挺满意的,各组组内平均值确实都达到了平均,但是忽略了一个问题,要求各组个数差不多,所以一求和就发现,有的组特别多,有的组特别少。崩溃ing
后来又想了一个v2版本——随机分组版。
每个维度可以分为两种,高于平均值和低于平均值的。比如送货件数,高于平均值的标记为“+”,低于平均值的标记为“-”。退货件数、送货件数、退货单数、送货单数(4个维度)。
例:
退货件数、送货件数、退货单数、送货单数都高于平均值的就是(++++)、
退货件数、送货件数高于平均值,退货单数、送货单数低于平均值就是(++--)
一共(2^4=16)16种组合,每种组合里面按随机的方法分成k组。再将16个组合里面相同标记的组成同组。所以最后每个组里面,既有(----)这种,也有(++++)、(-+-+)。这种随机抽取的应该能达到平均的效果。不过因为某些原因没有写代码验证行不行。
最后就是终于想出一种相对而言比较好的分类算法。
v3版——Z分数法
先上代码
import pandas as pd
from sklearn import preprocessing as pp
def f_class(row):
print(row)
if __name__ == '__main__':
# 组的个数
k = int(input("要分多少组?\n"))
# 标记list 长度k
assignments = [None]*k
# 读取数据
open_path = r' '
data = pd.read_excel(open_path ,sheet_name='Sheet1',header = 0 )
# 设置新索引
data = data.set_index('供应商')
# 缺失值补0
data.fillna(0,inplace=True)
data = data.astype('float')
# 标准化 Z分数
scaled_data = data.copy()
scaled_data.ix[:, :] = pp.scale(data.ix[:, :])
# Z分数都加100 求和 排序降序
scaled_data.ix[:, :] = scaled_data.ix[:, :]+100
scaled_data['score'] = scaled_data['退货件数']+scaled_data['送货件数']+scaled_data['退货单数']+scaled_data['单数']
scaled_data.sort_values('score',inplace=True,ascending=False)
# 关键部分
#
scaled_data['mark'] = None
for x in range(len(scaled_data)):
for i in range(k):
assignments[i] = [item1[4] for (idx1, item1) in scaled_data.iterrows() if item1[5] == i]
assignments[i] = sum(assignments[i])
# 最小索引
min_index = assignments.index(min(assignments))
scaled_data.ix[x,'mark'] = min_index
print(scaled_data)
save_path = r' '
write = pd.ExcelWriter(save_path)
scaled_data.to_excel(write, sheet_name='分组结果', header=True, index=True)
data.to_excel(write, sheet_name='源数据', header=True, index=True)
write.save()
print('完成')
代码做法
①将四个维度全部数据标准化,即算出对应的Z分数,
②将全部Z分数加上100,消除负数,
③求出每行四个维度Z分数的合计——Z分数合计,按降序排序
④从第一个开始,算出当前哪个组别Z分数合计最小,将改行数据分配给这个组别。
思路
这个解法最开始是看的老大的思路,他将所有供应商按送货件数排好,然后1~6这样循环。
这种方法简单粗暴但是缺点很明显—— 第一组 > 第二组> ... > 最后一组
改进一下就是1~ 6一次后6~ 1一次然后不断循环。但是效果也不够好。
最后综合这几天的思考,最终想到这个解法。
可以看到,前面①~③都是为④准备。
最后出来的效果还可以,但这是什么原因呢?
首先要明白什么是Z分数,或者说数据标准化,也有称为归一化。这样处理数据之后可以消除单位的影响,Z分数可以告诉你,这个数据离平均值大概有多少。大于0表示高于平均值,越大离平均值越远,小于0表示低于平均值,越小离平均值越远。
第二,因为Z分数有正有负,但Z分数不会太大,加上100后全部都是正数(其实+10就可以了,Z分数>3或<-3已经算是异常值了),将四个维度的Z分数合计,这个Z分数合计一定程度上可以理解为将四个维度缩小到一个维度,用这一个数据来表示数据的相对大小。然后需要排序,从大到小分。
最后,关键这点:算出当前数据哪个组的Z分数合计最小,将当前数据分配给这个组。可以理解为一种补偿机制,这样可以保证最后每个组的和差不多。
所以这个解法的关键点就是,
①将四维数据降为一维,用这个一维一定程度上代表数据的位置。
②先排序,再分组(补偿)。
而缺点也明显:排序后的数据间隔不能太大。否则可能末尾数据全部到某一个组去了。
也许还有更好的解法,但这个是目前我能想到且能实现的解法了。这次要解决的问题不像之前,这次的解法各种各样,网上也参考了很多但直觉觉得不对。
这次也算是把以前了解的比较不常见的点都复习了一遍
统计学的Z分数(数据标准化)、数据降维、向量的距离。
不过后来想想,好像不用消除负数也可以啊(」゜ロ゜)」
网友评论