Без предвзятости верните список n случайных положительных чисел ( >= 0), чтобы их сумма == total_sum

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

Есть ли способ сделать выбор числа более эффективным?

#!/usr/bin/python
'''
  Generate a list of 'numbs' positive random numbers whose sum = 'limit_sum'
'''

import random


def gen_list(numbs, limit_sum):
  my_sum = []
  for index in range(0, numbs):
    if index == numbs - 1:
      my_sum.append(limit_sum - sum(my_sum))
    else:
      my_sum.append(random.uniform(0, limit_sum - sum(my_sum)))

  return my_sum

#test
import pprint
pprint.pprint(gen_list(5, 20))
pprint.pprint(gen_list(10, 200))
pprint.pprint(gen_list(0, 30))
pprint.pprint(gen_list(1, 10))

ВЫХОД

## output

[0.10845093828525609,
 16.324799712999706,
 0.08200162072303821,
 3.4534885160590041,
 0.031259211932997744]

[133.19609626532952,
 47.464880208741029,
 8.556082341110228,
 5.7817325913462323,
 4.6342577008233716,
 0.22532341156764768,
 0.0027495225618908918,
 0.064738336208217895,
 0.028888697891734455,
 0.045250924420116689]

[]

[10]

Ответ 1

Хорошо, мы будем решать проблему, предполагая, что требование состоит в том, чтобы создать случайный вектор длины N, который равномерно распределен над допустимым пространством, пересчитывается следующим образом:

Учитывая

  • желаемая длина L,
  • желаемая общая сумма S,
  • диапазон допустимых значений [0, B] для каждого скалярного значения,

порождают случайный вектор V длины N такой, что случайная величина V равномерно распределена по всему разрешенному пространству.


Мы можем упростить задачу, заметив, что мы можем вычислить V = U * S, где U - подобный случайный вектор с нужной суммой 1 и диапазон допустимых значений [0, b], где b = B/S. Значение b должно быть между 1/N и 1.


Сначала рассмотрим N = 3. Пространство допустимых значений {U} является частью плоскости, перпендикулярной вектору [1 1 1], которая проходит через точку [1/3 1/3 1/3] и которая лежит внутри куба, компоненты которого находятся в диапазоне от 0 до b. Этот набор точек {U} имеет форму шестиугольника.

(TBD: picture. Я не могу создать его прямо сейчас, мне нужен доступ к MATLAB или другой программе, которая может делать 3D-графики. Моя установка Octave не может.)

Лучше всего использовать ортонормированную взвешивающую матрицу W (см. мой другой ответ) с одним вектором = [1 1 1]/sqrt (3). Одна такая матрица

octave-3.2.3:1> A=1/sqrt(3)
   A =  0.57735
octave-3.2.3:2> K=1/sqrt(3)/(sqrt(3)-1)
   K =  0.78868
octave-3.2.3:3> W = [A A A; A 1-K -K; A -K 1-K]
   W =

     0.57735   0.57735   0.57735
     0.57735   0.21132  -0.78868
     0.57735  -0.78868   0.21132

которая, опять же, ортонормирована (W * W = I)

Если вы рассматриваете точки куба [0 0 b], [0bb], [0 b 0], [bb 0], [b 0 0] и [b 0 b], они образуют шестиугольник и все это расстояние b * sqrt (2/3) от диагонали куба. Они не удовлетворяют рассматриваемой проблеме, но полезны через минуту. Остальные две точки [0 0 0] и [b b b] находятся на диагонали куба.

Ортонормальная взвешивающая матрица W позволяет нам создавать точки, равномерно распределенные внутри {U}, так как ортонормированные матрицы являются преобразованиями координат, которые вращаются/отражаются и не масштабируются или не искажаются.

Мы будем генерировать точки, равномерно распределенные в системе координат, определяемой 3 векторами W. Первая компонента - ось диагонали куба. Сумма U-компонент полностью зависит от этой оси и вовсе не от остальных. Поэтому координата вдоль этой оси должна быть 1/sqrt (3), которая соответствует точке [1/3, 1/3, 1/3].

Остальные два компонента находятся в направлениях, перпендикулярных диагонали куба. Поскольку максимальное расстояние от диагонали составляет b * sqrt (2/3), мы будем генерировать равномерно распределенные числа (u, v) между -b * sqrt (2/3) и + b * sqrt (2/3).

Это дает нам случайную величину U '= [1/sqrt (3) u v]. Затем мы вычисляем U = U '* W. Некоторые из результирующих точек будут за пределами допустимого диапазона (каждая компонента U должна находиться между 0 и b), и в этом случае мы отвергаем это и начинаем.

Другими словами:

  • Генерировать независимые случайные переменные u и v, каждый из которых равномерно распределен между -b * sqrt (2/3) и + b * sqrt (3).
  • Вычислить вектор U '= [1/sqrt (3) u v]
  • Вычислить U = U '* W.
  • Если какая-либо из U-компонент находится вне диапазона [0, b], отклоните это значение и вернитесь к шагу 1.
  • Вычислить V = U * S.

Решение аналогично для более высоких размеров (равномерно распределенных точек внутри части гиперплоскости, перпендикулярной главной диагонали гиперкуба):

Предварительно рассчитайте весовую матрицу W ранга N.

  • Генерировать независимые случайные величины u 1, u 2,... u N-1, каждый из которых равномерно распределен между -b * k ( N) и + b * k (N).
  • Вычислить вектор U '= [1/N u 1, u 2,... u N-1]
  • Вычислить U = U '* W. (есть ярлыки для фактического создания и умножения на W.)
  • Если какая-либо из U-компонент находится вне диапазона [0, b], отклоните это значение и вернитесь к шагу 1.
  • Вычислить V = U * S.

Диапазон k (N) является функцией от N, которая представляет максимальное расстояние вершин гиперкуба стороны 1 от его главной диагонали. Я не уверен в общей формуле, но это sqrt (2/3) для N = 3, sqrt (6/5) для N = 5, там, вероятно, есть для нее формула.

Ответ 2

Почему бы не просто сгенерировать правильное число равномерно распределенных случайных чисел, поднять их и масштабировать?

EDIT: Чтобы быть немного понятнее: вы хотите, чтобы N чисел суммировались с S? Поэтому создайте N равномерно распределенных случайных чисел на интервале [0,1) или независимо от того, что производит ваш RNG. Добавьте их, они будут суммировать s (скажем), тогда как вы хотите, чтобы они суммировали S, поэтому умножайте каждое число на S/s. Теперь числа равномерно распределены случайным образом на [0, S/s), я думаю.

Ответ 3

Вот как бы я это сделал:

  • Создание n-1 случайных чисел, все в диапазоне [0, max]
  • Сортировка этих чисел
  • Для каждой пары, состоящей из i-го и (i + 1) -ного числа в отсортированном списке, создайте интервал (i, я + 1) и вычислите его длину. Последний интервал начинается с последнего номера и заканчивается на max, и первый интервал начинается с 0 и заканчивается первым номером в списке.

Теперь длины этих интервалов всегда будут суммироваться до max, так как они просто представляют сегменты внутри [0, max].

Код (в Python):

#! /usr/bin/env python
import random

def random_numbers(n,sum_to):
    values=[0]+[random.randint(0,sum_to) for i in xrange(n-1)]+[sum_to]
    values.sort()
    intervals=[values[i+1]-values[i] for i in xrange(len(values)-1)]
    return intervals

if __name__=='__main__':
    print random_numbers(5,100)

Ответ 4

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

(* rigorous: проблема с другими подходами заключается в том, что вы можете получить "длинные хвосты" в своих дистрибутивах - другими словами, это редко, но возможно иметь выбросы, которые сильно отличаются от ожидаемого результата)

  • Генерировать N-1 независимые и идентично распределенные (IID) гауссовские случайные величины v 0, v 1, v 2,... v N-1, чтобы соответствовать степеням свободы вашей проблемы N-1.
  • Создайте вектор-столбец V, где V = [0 v 0, v 1, v 2,... v N-1суб > ] T
  • Используйте фиксированную взвешивающую матрицу W, где W состоит из ортонормированной матрицы **, верхняя строка которой [1 1 1 1 1 1 1... 1]/sqrt (N).
  • Ваш выходной вектор является произведением WV + SU/N, где S - искомая сумма, а U - вектор столбца 1. Другими словами, i-я выходная переменная = точечное произведение (строка #i матрицы W) и вектор-столбец V, добавленная к S/N.

Стандартное отклонение каждой выходной переменной будет (я считаю, не могу проверить прямо сейчас) sqrt (N/N-1) * стандартное отклонение входных случайных величин.

** ортонормированная матрица: это трудная часть, я ставлю вопрос на math.stackexchange.com и там простую матрицу W, которая работает, и может определяться алгоритмически только с тремя различными значениями, поэтому вам фактически не нужно создавать матрицу.

W является отражателем домохозяйства vw, где v = [sqrt (N), 0, 0, 0,...] и w = [1 1 1 1 1... 1] может быть определено:

W(1,i) = W(i,1) = 1/sqrt(N)
W(i,i) = 1 - K   for i >= 2 
W(i,j) = -K      for i,j >= 2, i != j
K = 1/sqrt(N)/(sqrt(N)-1)

Проблема с подходом Mark:

Почему бы не просто сгенерировать правильное число равномерно распределенных случайных чисел, поднять их и масштабировать?

заключается в том, что если вы это сделаете, вы получите "длинный хвост". Вот пример в MATLAB:

 >> X = rand(100000,10);
 >> Y = X ./ repmat(sum(X,2),1,10);
 >> plot(sort(Y))

Я создал 100 000 наборов N = 10 чисел в матрице X и создал матрицу Y, где каждая строка Y является соответствующей строкой X, деленной на ее сумму (так что каждая строка Y суммируется до 1.0)

Вычисление отсортированных значений Y (каждый столбец, отсортированный отдельно) дает примерно такое же кумулятивное распределение:

alt text

Истинное равномерное распределение даст прямую линию от 0 до максимального значения. Вы заметите, что это немного похоже на истинное равномерное распределение, кроме как в конце, где есть длинный хвост. Там избыток чисел генерируется от 0,2 до 0,5. Хвост ухудшается при больших значениях N, потому что хотя среднее значение чисел уменьшается (среднее value = 1/N), максимальное значение остается равным 1.0: допустим вектор, состоящий из 9 значений 0.0 и 1 значения 1.0. и может быть сгенерирован таким образом, но патологически редок.

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


Одно исправление, которое оставляет вещи разнородно распределенными без длинного хвоста, выглядит следующим образом:

  • Создать вектор V = N равномерно распределенных случайных чисел от 0.0 до 1.0.
  • Найдите их сумму S и максимальное значение M.
  • Если S < k * M (максимальное значение слишком велико), вернитесь к шагу 1. Я не уверен, какое значение использовать для k, возможно k = N/2?
  • Вывести вектор V * S желаемый/S

Пример в MATLAB для N = 10:

 >> X = rand(100000,10);
 >> Y = X ./ repmat(sum(X,2),1,10);
 >> i = sum(X,2)>(10/2)*max(X,[],2);
 >> plot(sort(Y(i,:)))

alt text

Ответ 5

Следующее довольно просто и возвращает равномерные результаты:

def gen_list(numbs, limit_sum):
    limits = sorted([random.uniform(0, limit_sum) for _ in xrange(numbs-1)])
    limits = [0] + limits + [limit_sum]
    return [x1-x0 for (x0, x1) in zip(limits[:-1], limits[1:])]

Идея состоит в том, что если вам нужно, скажем, 5 чисел между 0 и 20, вы можете просто поставить 4 "пределы" между 0 и 20, и вы получите раздел (0, 20) интервал. Случайные числа, которые вы хотите, представляют собой просто длины из 5 интервалов в отсортированном списке [0, random1, random2, random3, random4, 20].

PS: упс! выглядит так же, как и MAK-ответ, хотя и закодирован без использования индексов!

Ответ 6

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

import numpy.random, numpy
total_sum = 20
n = 6

v = numpy.random.multinomial(total_sum, numpy.ones(n)/n)

Как поясняет многокомпонентная документация, вы свернули справедливую шестистороннюю кость двадцать раз. v содержит шесть чисел, указывающих количество раз, когда каждая сторона кости была поднята. Естественно, элементы v должны суммироваться до двадцати. Здесь шесть равно n, а двадцать - total_sum.

С многочленом вы можете имитировать и несправедливые кости, что очень полезно в некоторых случаях.

Ответ 7

Вы можете сохранить текущую сумму, а не называть sum(my_sum) несколько раз.