Python 对对象进行分组以实现所有组的相似平均特性

Python 对对象进行分组以实现所有组的相似平均特性,python,grouping,combinations,mean,Python,Grouping,Combinations,Mean,我有一个对象集合,每个对象都有一个数字“权重”。我想创建这些对象的组,使每组对象权重的算术平均值大致相同 这些组不一定有相同数量的成员,但组的大小将在一个组内。就数量而言,将有50到100个对象,最大组大小约为5个 这是一种众所周知的问题吗?这似乎有点像背包或分区问题。是否知道有效的算法可以解决这个问题 作为第一步,我创建了一个python脚本,通过按权重对对象进行排序,对这些对象进行分组,然后将每个子组的一个成员分配给最后的一个组,从而实现了平均权重的粗略等价 我喜欢用python编程,所以如

我有一个对象集合,每个对象都有一个数字“权重”。我想创建这些对象的组,使每组对象权重的算术平均值大致相同

这些组不一定有相同数量的成员,但组的大小将在一个组内。就数量而言,将有50到100个对象,最大组大小约为5个

这是一种众所周知的问题吗?这似乎有点像背包或分区问题。是否知道有效的算法可以解决这个问题

作为第一步,我创建了一个python脚本,通过按权重对对象进行排序,对这些对象进行分组,然后将每个子组的一个成员分配给最后的一个组,从而实现了平均权重的粗略等价

我喜欢用python编程,所以如果现有的包或模块能够实现部分功能,我很乐意听到这些

感谢您的帮助和建议。

您可以尝试使用:

上面的代码生成N个权重的随机序列。 它用于将序列划分为靠近的
k
数字簇。如果失真高于阈值,则重新计算kmeans,并增加
k
。重复该操作,直到失真低于给定阈值

它产生如下集群:

0: [4.9062151907551366]
1: [13.545565038022112, 12.283828883935065]
2: [17.395300245930066]
3: [28.982058040201832, 30.032607500871023, 31.484125759701588]
4: [35.449637591061979]
5: [43.239840915978043, 48.079844689518424, 40.216494950261506]
6: [52.123246083619755, 53.895726546070463]
7: [80.556052179748079, 80.925071671718413, 75.211470587171803]
8: [86.443868931310249, 82.474064251040375, 84.088655128258964]
9: [93.525705849369416]
注意,k-means聚类算法使用随机猜测来最初选择
k
组的中心。这意味着同一代码的重复运行可能会产生不同的结果,特别是如果权重没有将自己划分为明显不同的组


您还必须旋转threshold参数以生成所需的组数

您还可以尝试一种基于质心的链接算法,该算法可以实现相同的效果

有关代码和理解,请参阅


UPGMA(又名质心基)是您可能想要做的

接下来的程序是一个低成本的启发式程序。它所做的是在“bucket”之间分配值,通过在一轮排序列表的一端选择值,在下一轮排序列表的另一端选择值,将大值与小值放在一起。以循环方式进行分发可以确保满足关于每个bucket的元素数量的规则。它是一种启发式算法,而不是一种算法,因为它倾向于产生好的解决方案,但不能保证不存在更好的解决方案

从理论上讲,如果有足够的值,并且这些值是均匀分布或正态分布的,那么很可能只是随机地将这些值放置在桶中,将导致桶的平均值相同。假设数据集很小,这种启发式方法提高了获得良好解决方案的机会。更多地了解数据集的大小和统计分布将有助于设计更好的启发式或算法

from random import randint, seed
from itertools import cycle,chain

def chunks(q, n):
    q = list(q)
    for i in range(0, len(q), n):
       yield q[i:i+n]

def shuffle(q, n):
    q = list(q)
    m = len(q)//2
    left =  list(chunks(q[:m],n))
    right = list(chunks(reversed(q[m:]),n)) + [[]]
    return chain(*(a+b for a,b in zip(left, right)))

def listarray(n):
    return [list() for _ in range(n)]

def mean(q):
    return sum(q)/len(q)

def report(q):
    for x in q:
        print mean(x), len(x), x

SIZE = 5
COUNT= 37

#seed(SIZE)
data = [randint(1,1000) for _ in range(COUNT)]
data = sorted(data)
NBUCKETS = (COUNT+SIZE-1) // SIZE

order = shuffle(range(COUNT), NBUCKETS)
posts = cycle(range(NBUCKETS))
buckets = listarray(NBUCKETS)
for o in order:
    i = posts.next()
    buckets[i].append(data[o])
report(buckets)
print mean(data)
由于排序步骤,复杂性是对数的。以下是示例结果:

439 5 [15, 988, 238, 624, 332]
447 5 [58, 961, 269, 616, 335]
467 5 [60, 894, 276, 613, 495]
442 5 [83, 857, 278, 570, 425]
422 5 [95, 821, 287, 560, 347]
442 4 [133, 802, 294, 542]
440 4 [170, 766, 301, 524]
418 4 [184, 652, 326, 512]
440
请注意,桶大小的要求占主导地位,这意味着如果原始数据中的方差较大,则平均值不会接近。您可以尝试使用此数据集:

data = sorted(data) + [100000]
包含
100000
的桶将至少获得另外3个基准


我想出了一个启发性的想法,那就是如果一群孩子递上一包不同面额的钞票,并让他们按照游戏规则分享,他们会做什么。这在统计上是合理的,O(log(N))。

这听起来也有点像箱子包装问题。无论是否,您都应该认为这在计算上很难获得最佳解决方案。感谢您关于使用聚类算法的建议。然而,我不知道这种方法将如何工作:正如我在最初的帖子中提到的,我需要有一定数量的组,每个组都有特定数量的成员。这些数字是根据每个组指定的最大对象数nmax以及任何组的对象数都不少于(nmax-1)的要求计算的。例如,假设我总共有28个对象,每个组最多需要5个对象。这就导致了6个组,每个组的对象数量如下:[5,5,5,5,4,4]。@cytochrome:哦,我不知道组的大小有这么严格的要求。在这种情况下,似乎真的很少有备选解决方案。例如,在上面的示例中,解决方案的组大小必须是[5,5,5,5,4,4]的某种排列。(只有
6选择2
=6*5/2=15种可能性)。您可以对权重进行排序,并根据每个可能的排列将它们分组,然后选择失真最小的排列。谢谢,unutbu。虽然我的问题比示例更大,但使用您推荐的策略应该仍然可以管理。我目前的代码已经完成了这一部分,因此更改应该很简单。>>任何组的对象都不会少于(nmax-1)对象。如果它是27个对象,nmax==5呢?
data = sorted(data) + [100000]