Как задать верхние и нижние границы при использовании numpy.random.normal
Я хочу выбрать значения из нормального распределения, которые всегда находятся в диапазоне от 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 ответ(ов)
Я наткнулся на этот пост, когда искал способ получить серию значений, 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.
Ваш код функции для генерации списка значений в заданном диапазоне с использованием numpy.random.normal
в целом выглядит неплохо, но есть несколько моментов, которые можно улучшить.
Избыточные итерации: В вашем коде вы дважды проходите по
initiallist
, чтобы найти индексы для минимального и максимального значений. Вместо этого вы можете использовать маскирование.Выбор значений: Вы можете упростить выбор значений, просто отфильтровав массив сразу, что значительно повысит производительность.
Шум в функции: Вместо того чтобы копировать список готовых значений и перетасовывать его в конце, вы можете сразу возвращать нужное количество значений.
Вот улучшенная версия функции:
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
применяется только один раз к конечному списку.
Эти изменения повышают производительность и читаемость кода. Надеюсь, это поможет!
Вот простая функция для выполнения этой задачи:
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 стандартных отклонения.
Если вы не хотите использовать 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
), и если какие-либо значения выходят за пределы диапазона, они повторно генерируются до тех пор, пока не будут соответствовать условиям.
Как вывести полный массив NumPy без обрезки?
Как получить доступ к i-му столбцу многомерного массива NumPy?
Наиболее эффективный способ применения функции к массиву NumPy
Индексация массива numpy с помощью списка кортежей
В NumPy, что делает выбор с помощью [:, None]?