Как генерировать случайные значения в диапазоне (-1, 1) так, чтобы общая сумма была 0? - PullRequest
0 голосов
/ 16 октября 2019

Если сумма равна 1, я мог бы просто разделить значения на их сумму. Однако этот подход неприменим, когда сумма равна 0.

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

Есть ли лучшие подходы?

Редактировать: длина массива может варьироваться (от 3 до нескольких сотен), но этодолжен быть исправлен перед отбором проб.

Ответы [ 4 ]

0 голосов
/ 16 октября 2019

Так как вам подходит метод генерации множества чисел и деления на сумму, почему бы не сгенерировать n / 2 положительных чисел, деленных на сумму. Затем сгенерируйте n / 2 отрицательных чисел и поделите на сумму?

Хотите случайную комбинацию от положительного до отрицательного? Сначала случайным образом генерируйте этот микс, а затем продолжайте

0 голосов
/ 16 октября 2019

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

def exact_sum_uniform_random(num, min_val=-1.0, max_val=1.0, epsilon=0.1):
    items = [random.uniform(min_val, max_val) for _ in range(num // 2)]
    opposites = [-x for x in items]
    if num % 2 != 0:
        items.append(0.0)
    for i in range(len(items)):
        diff = random.random() * epsilon
        if items[i] + diff <= max_val \
                and any(opposite - diff >= min_val for opposite in opposites):
            items[i] += diff
            modified = False
            while not modified:
                j = random.randint(0, num // 2 - 1)
                if opposites[j] - diff >= min_val:
                    opposites[j] -= diff
                    modified = True
    result = items + opposites
    random.shuffle(result)
    return result


random.seed(0)
x = exact_sum_uniform_random(3)
print(x, sum(x))
# [0.7646391433441265, -0.7686875811622043, 0.004048437818077755] 2.2551405187698492e-17

РЕДАКТИРОВАТЬ

Есливерхний и нижний пределы не являются строгими, простой способ построить последовательность с нулевой суммой состоит в том, чтобы нормализовать сумму двух отдельных последовательностей к 1 и -1 и объединить их вместе:

def norm(items, scale):
    return [item / scale for item in items]


def zero_sum_uniform_random(num, min_val=-1.0, max_val=1.0):
    a = [random.uniform(min_val, max_val) for _ in range(num // 2)]
    a = norm(a, sum(a))
    b = [random.uniform(min_val, max_val) for _ in range(num - len(a))]
    b = norm(b, -sum(b))
    result = a + b
    random.shuffle(result)
    return result


random.seed(0)
n = 3
x = exact_mean_uniform_random(n)
print(exact_mean_uniform_random(n), sum(x))
# [1.0, 2.2578843364303585, -3.2578843364303585] 0.0

Обратите внимание, что обав общем случае подходы не будут иметь равномерного распределения.

0 голосов
/ 16 октября 2019

Вы можете использовать sklearns Standardscaler. Он масштабирует ваши данные, чтобы иметь дисперсию 1 и среднее значение 0. Среднее значение 0 эквивалентно сумме 0.

from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
rand_numbers = StandardScaler().fit_transform(np.random.rand(100,1, ))

Если вы не хотите использовать sklearn, вы можете стандартизировать с помощьюС другой стороны, формула довольно проста:

rand_numbers = np.random.rand(1000,1, )
rand_numbers = (rand_numbers - np.mean(rand_numbers)) / np.std(rand_numbers)  

Проблема здесь заключается в дисперсии 1, которая приводит к числам больше 1 или меньше -1. Для этого вы делите массив на максимальное значение абс.

rand_numbers = rand_numbers*(1/max(abs(rand_numbers)))

Теперь у вас есть массив со значениями от -1 до 1 с суммой, действительно близкой к нулю.

print(sum(rand_numbers))
print(min(rand_numbers))
print(max(rand_numbers))

Вывод:

[-1.51822999e-14]
[-0.99356294]
[1.]

То, что у вас будет с этим решением, всегда будет либо один 1, либо один -1 в ваших данных. Если вы хотите избежать этого, вы можете добавить положительный случайный коэффициент к делению через максимальный абс. rand_numbers*(1/(max(abs(rand_numbers))+randomfactor))

Редактировать

Как упомянул @KarlKnechtel, деление на стандартное отклонение является избыточным с делением на максимальное абсолютное значение.

Вышесказанное можно сделать просто:

rand_numbers = np.random.rand(100000,1, )
rand_numbers = rand_numbers - np.mean(rand_numbers)
rand_numbers = rand_numbers / max(abs(rand_numbers))
0 голосов
/ 16 октября 2019

Я бы попробовал следующее решение:

def draw_randoms_while_sum_not_zero(eps):
    r = random.uniform(-1, 1)
    sum = r
    yield r
    while abs(sum) > eps:
        if sum > 0:
            r = random.uniform(-1, 0)
        else:
            r = random.uniform(0,1)
        sum += r
        yield r

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

Он будет выдавать (лениво возвращать) случайные числа по мере необходимости, если они не суммируются до 0 ± eps

epss = [0.1, 0.01, 0.001, 0.0001, 0.00001]

for eps in epss:
    lengths = []
    for _ in range(100):
        lengths.append(len(list(draw_randoms_while_sum_not_zero(eps))))
    print(f'{eps}: min={min(lengths)}, max={max(lengths)}, avg={sum(lengths)/len(lengths)}')

Результаты:

0.1: min=1, max=24, avg=6.1
0.01: min=1, max=174, avg=49.27
0.001: min=4, max=2837, avg=421.41
0.0001: min=5, max=21830, avg=4486.51
1e-05: min=183, max=226286, avg=48754.42
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...