группировка объектов для достижения одинакового среднего свойства для всех групп - PullRequest
5 голосов
/ 16 декабря 2010

У меня есть коллекция объектов, каждый из которых имеет числовой «вес».Я хотел бы создать группы этих объектов так, чтобы у каждой группы было приблизительно одинаковое среднее арифметическое значений веса объекта.

Группы не обязательно будут иметь одинаковое количество членов, но размер групп будет в пределаходин из другого.С точки зрения чисел, будет от 50 до 100 объектов, а максимальный размер группы будет около 5.

Это хорошо известный тип проблемы?Это похоже на проблему с рюкзаком или разделом.Известны ли эффективные алгоритмы для ее решения?

В качестве первого шага я создал скрипт на python, который достигает очень грубой эквивалентности средних весов, сортируя объекты по весу, подгруппировав эти объекты, а затем распределяя член каждого из них.подгруппа в одну из заключительных групп.

Мне удобно программировать на python, поэтому, если существуют существующие пакеты или модули для реализации части этой функциональности, я был бы рад услышать о них.

СпасибоВас за вашу помощь и предложения.

Ответы [ 3 ]

2 голосов
/ 30 декабря 2010

Следующая программа представляет собой недорогую эвристику. Он распределяет значения по «корзинам», помещая большие значения вместе с маленькими, выбирая значения с одного конца отсортированного списка в одном раунде, а с другого - в следующем. Распределение в циклическом цикле гарантирует соблюдение правил о количестве элементов в каждой группе. Это эвристический алгоритм, а не алгоритм, потому что он дает хорошие решения, но без гарантии, что лучшие не существует.

Теоретически, если имеется достаточно значений, и они распределены равномерно или нормально, то есть вероятность, что просто случайное размещение значений в сегментах приведет к одинаковым средствам для блоков. Предполагая, что набор данных мал, эта эвристика повышает шансы на хорошее решение. Зная больше о размере и статистическом распределении наборов данных, можно было бы разработать лучшую эвристику или алгоритм.

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)).

1 голос
/ 16 декабря 2010

Вы также можете попробовать алгоритм связывания на основе центроидов, который достигает того же самого.

См. это для кода и это для понимания.

UPGMA (он же на базе центроида) - это то, что вы, вероятно, хотите сделать.

1 голос
/ 16 декабря 2010

Вы можете попробовать использовать k-средних кластеров :

import scipy.cluster.vq as vq
import collections
import numpy as np

def auto_cluster(data,threshold=0.1,k=1):
    # There are more sophisticated ways of determining k
    # See http://en.wikipedia.org/wiki/Determining_the_number_of_clusters_in_a_data_set
    data=np.asarray(data)
    distortion=1e20
    while distortion>threshold:
        codebook,distortion=vq.kmeans(data,k)
        k+=1   
    code,dist=vq.vq(data,codebook)    
    groups=collections.defaultdict(list)
    for index,datum in zip(code,data):
        groups[index].append(datum)
    return groups

np.random.seed(784789)
N=20
weights=100*np.random.random(N)
groups=auto_cluster(weights,threshold=1.5,k=N//5)
for index,data in enumerate(sorted(groups.values(),key=lambda d: np.mean(d))):
    print('{i}: {d}'.format(i=index,d=data))

Приведенный выше код генерирует случайную последовательность из N весов.Он использует scipy.cluster.vq.kmeans для разделения последовательности на 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-средних использует случайные догадки для первоначального выбора центровk групп.Это означает, что повторные прогоны одного и того же кода могут давать разные результаты, особенно если весовые коэффициенты не разделяются на четко различающиеся группы.

Вам также нужно будет повернуть параметр порога, чтобы получить желаемое количество групп,

...