0

Как задать верхние и нижние границы при использовании numpy.random.normal

10

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

В настоящее время я использую следующую функцию:

def blockedgauss(mu,sigma):
    while True:
        numb = random.gauss(mu,sigma)
        if (numb > 0 and numb < 1):
            break
    return numb

Эта функция выбирает значение из нормального распределения, а затем отбрасывает его, если оно находится вне диапазона от 0 до 1. Однако мне кажется, что существует более эффективный способ сделать это. Как можно улучшить эту функцию или использовать другой подход для получения значений из нормального распределения в заданном диапазоне?

4 ответ(ов)

0

Я наткнулся на этот пост, когда искал способ получить серию значений, sampled из нормального распределения, ограниченного между нулем и единицей (т.е. вероятностями). Чтобы помочь тем, кто столкнется с аналогичной проблемой, хочу отметить, что в библиотеке scipy.stats есть встроенная функция truncnorm с методом .rvs.

Таким образом, если вам нужно получить 100,000 выборок со средним 0.5 и стандартным отклонением 0.1:

import scipy.stats
lower = 0
upper = 1
mu = 0.5
sigma = 0.1
N = 100000

samples = scipy.stats.truncnorm.rvs(
          (lower-mu)/sigma, (upper-mu)/sigma, loc=mu, scale=sigma, size=N)

Это дает поведение, очень похожее на numpy.random.normal, но в пределах заданных ограничений. Использование встроенного метода будет значительно быстрее, чем использование циклов для получения выборок, особенно при больших значениях N.

0

Ваш код функции для генерации списка значений в заданном диапазоне с использованием numpy.random.normal в целом выглядит неплохо, но есть несколько моментов, которые можно улучшить.

  1. Избыточные итерации: В вашем коде вы дважды проходите по initiallist, чтобы найти индексы для минимального и максимального значений. Вместо этого вы можете использовать маскирование.

  2. Выбор значений: Вы можете упростить выбор значений, просто отфильтровав массив сразу, что значительно повысит производительность.

  3. Шум в функции: Вместо того чтобы копировать список готовых значений и перетасовывать его в конце, вы можете сразу возвращать нужное количество значений.

Вот улучшенная версия функции:

import numpy as np

def truncnormal(meanv, sd, minv, maxv, n):
    # Генерируем выборку, фильтруем по заданному диапазону
    initiallist = []
    
    while len(initiallist) < n:
        samples = np.random.normal(meanv, sd, n)  # Генерация выборки
        # Фильтрация значений в диапазоне
        filtered_samples = samples[(samples >= minv) & (samples <= maxv)]
        initiallist.extend(filtered_samples)
        
    np.random.shuffle(initiallist)  # Перетасовка
    finallist = initiallist[:n]  # Обрезаем до нужного размера

    print(len(finallist), min(finallist), max(finallist))

truncnormal(10, 3, 8, 11, 10000)

В этом варианте кода:

  • Используется одно условие для фильтрации значений, что упрощает логику;
  • Уменьшается общее количество циклов, так как значения фильтруются в одном проходе;
  • np.random.shuffle применяется только один раз к конечному списку.

Эти изменения повышают производительность и читаемость кода. Надеюсь, это поможет!

0

Вот простая функция для выполнения этой задачи:

def norm_range(s, e, n, nsd=3):
    """Возвращает нормально распределенные элементы в заданном диапазоне.

    Аргументы:
    s -- начальное значение диапазона
    e -- конечное значение диапазона
    n -- требуемое количество элементов
    nsd -- количество стандартных отклонений в диапазоне (по умолчанию 3)
    """
    m = (s + e) / 2  # среднее
    sd = (e - s) / (nsd * 2)  # стандартное отклонение
    r = np.random.normal(m, sd, n)  # генерируем необходимые элементы
    r = r[(r >= s) & (r <= e)]  # отсекаем элементы за пределами диапазона
    while len(r) < n:
        rex = np.random.normal(m, sd, 2 * (n - len(r)))  # генерируем дополнительные элементы
        r = np.append(r, rex[(rex >= s) & (rex <= e)])  # отсекаем те, что за пределами, и добавляем
    return np.random.choice(r, size=n, replace=False)  # возвращаем n элементов

Эта функция возвращает n нормально распределенных элементов со средним значением в центре заданного диапазона и, по умолчанию, охватывает 3 стандартных отклонения.

0

Если вы не хотите использовать truncnorm из библиотеки scipy, вот простая функция на NumPy, которая повторно генерирует выборки, выходящие за пределы заданного диапазона:

import numpy as np

def limited_normal(mu, sig, size, lo=-np.inf, hi=np.inf):
    A = np.random.normal(mu, sig, size)
    bad = np.where((A < lo) | (A > hi))
    n_bad = len(bad[0])
    if n_bad:
        A[bad] = limited_normal(mu, sig, n_bad, lo, hi)
    return A

print(limited_normal(1, 4, (4, 4), -2, -1))

Здесь mu и sig должны быть скалярами. Функция создает массив выборок из нормального распределения с заданными средним (mu) и стандартным отклонением (sig), и если какие-либо значения выходят за пределы диапазона, они повторно генерируются до тех пор, пока не будут соответствовать условиям.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь