我想生成n个随机数,例如n=200
,其中可能值的范围在2到40之间,平均值为12,中位数为6.5。
我到处搜索,我找不到解决方法。我尝试了以下脚本,因为它适用于20等小数字,对于大数字,它需要很长时间才能返回结果。
n=200
x = np.random.randint(0,1,size=n) # initalisation only
while True:
if x.mean() == 12 and np.median(x) == 6.5:
break
else:
x=np.random.randint(2,40,size=n)
任何人都可以通过改进这一点来帮助我获得快速结果,即使n = 5000左右?
答案 0 :(得分:4)
获得结果非常接近您想要的结果的一种方法是生成两个单独的随机范围,长度为100,满足您的中值约束,并包括所有期望的数字范围。然后通过连接数组,平均值将在12左右但不等于12.但是因为它只是意味着你正在处理你只需通过调整其中一个数组来生成预期结果。 / p>
In [162]: arr1 = np.random.randint(2, 7, 100)
In [163]: arr2 = np.random.randint(7, 40, 100)
In [164]: np.mean(np.concatenate((arr1, arr2)))
Out[164]: 12.22
In [166]: np.median(np.concatenate((arr1, arr2)))
Out[166]: 6.5
以下是一个矢量化且非常优化的解决方案,针对使用for循环或python级代码的任何其他解决方案,通过约束随机序列创建:
import numpy as np
import math
def gen_random():
arr1 = np.random.randint(2, 7, 99)
arr2 = np.random.randint(7, 40, 99)
mid = [6, 7]
i = ((np.sum(arr1 + arr2) + 13) - (12 * 200)) / 40
decm, intg = math.modf(i)
args = np.argsort(arr2)
arr2[args[-41:-1]] -= int(intg)
arr2[args[-1]] -= int(np.round(decm * 40))
return np.concatenate((arr1, mid, arr2))
演示:
arr = gen_random()
print(np.median(arr))
print(arr.mean())
6.5
12.0
该功能背后的逻辑:
为了让我们拥有一个具有该标准的随机数组,我们可以将3个数组连接在一起arr1
,mid
和arr2
。 arr1
和arr2
各持有99个项目,mid
包含2个项目6和7,因此最终结果为中位数6.5。现在我们创建两个随机数组,每个数组的长度为99.我们需要做的是使结果具有12均值,即找到当前总和与12 * 200
之间的差异,并从N个最大数字中减去结果在这种情况下,我们可以从arr2
中选择它们并使用N=50
。
编辑:
如果在结果中有浮点数不成问题,您实际上可以缩短函数,如下所示:
import numpy as np
import math
def gen_random():
arr1 = np.random.randint(2, 7, 99).astype(np.float)
arr2 = np.random.randint(7, 40, 99).astype(np.float)
mid = [6, 7]
i = ((np.sum(arr1 + arr2) + 13) - (12 * 200)) / 40
args = np.argsort(arr2)
arr2[args[-40:]] -= i
return np.concatenate((arr1, mid, arr2))
答案 1 :(得分:2)
在这里,您希望中值小于平均值。这意味着统一分布是不合适的:你需要很多小的值而不是很好的值。
具体而言,您希望与大于或等于7的值的数量相同或更多的值6。
确保中位数为6.5的简单方法是在[2-6]范围内具有与[7-40]中相同的数值。如果您在两个范围内选择了均匀分布,那么理论均值为13.75,与所需的12不太远。
权重的微小变化可以使理论均值更接近:如果我们使用[5,4,3,2,1,1,...,1]来表示random.choices
的相对权重在[7,8,...,40]范围内,我们发现该范围的理论均值为19.98,足够接近预期的20。
示例代码:
>>> pop1 = list(range(2, 7))
>>> pop2 = list(range(7, 41))
>>> w2 = [ 5, 4, 3, 2 ] + ( [1] * 30)
>>> r1 = random.choices(pop1, k=2500)
>>> r2 = random.choices(pop2, w2, k=2500)
>>> r = r1 + r2
>>> random.shuffle(r)
>>> statistics.mean(r)
12.0358
>>> statistics.median(r)
6.5
>>>
所以我们现在有一个5000值的分布,其中值正好为6.5,平均值为12.0358(这个 随机,另一个测试会给出略微不同的值)。如果我们想要一个精确的平均值12,我们只需要调整一些值。这里sum(r)
是60179,应该是60000,所以我们必须减少175个值,这两个值都不是2(超出范围)而不是7(会改变中位数)。
最后,可能的生成器函数可能是:
def gendistrib(n):
if n % 2 != 0 :
raise ValueError("gendistrib needs an even parameter")
n2 = n//2 # n / 2 in Python 2
pop1 = list(range(2, 7)) # lower range
pop2 = list(range(7, 41)) # upper range
w2 = [ 5, 4, 3, 2 ] + ( [1] * 30) # weights for upper range
r1 = random.choices(pop1, k=n2) # lower part of the distrib.
r2 = random.choices(pop2, w2, k=n2) # upper part
r = r1 + r2
random.shuffle(r) # randomize order
# time to force an exact mean
tot = sum(r)
expected = 12 * n
if tot > expected: # too high: decrease some values
for i, val in enumerate(r):
if val != 2 and val != 7:
r[i] = val - 1
tot -= 1
if tot == expected:
random.shuffle(r) # shuffle again the decreased values
break
elif tot < expected: # too low: increase some values
for i, val in enumerate(r):
if val != 6 and val != 40:
r[i] = val + 1
tot += 1
if tot == expected:
random.shuffle(r) # shuffle again the increased values
break
return r
速度非常快:我可以在不到0.02秒的时间内 timeit gendistrib(10000)
。但它不应该用于小分布(小于1000)
答案 2 :(得分:1)
好的,您正在查看具有不少于4个参数的分布 - 其中两个定义范围,两个负责所需的均值和中位数。
我可以从头脑中思考两种可能性:
截断正态分布,查看here了解详情。您已经定义了范围,并且必须从平均值和中值恢复μ和σ。它需要求解几个非线性方程,但在python中非常可行。可以使用https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.truncnorm.html
4参数Beta分发,有关详细信息,请参阅here。同样,从均值和中值恢复β分布中的α和β将需要求解几个非线性方程。通过https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.beta.html
更新
在这里你怎么做截断正常从平均到mu:Truncated normal with a given mean
答案 3 :(得分:0)
如果你有一堆具有正确的中位数和平均值的较小数组,你可以将它们组合起来产生一个更大的数组。
所以......你可以像现在一样预先生成较小的数组,然后将它们随机组合成更大的n。当然,这会产生一个偏向的随机样本,但听起来你只是想要一些近乎随机的东西。
这是一个工作(py3)代码,它生成一个大小为5000的样本,带有你想要的属性,它是从4,6,8,10,...,18的较小样本构建的。
注意,我改变了较小的随机样本的构建方式:如果中位数为6.5,则一半数字必须<= 6而一半> = 7,所以我们独立生成这两半。这大大加快了速度。
import collections
import numpy as np
import random
rs = collections.defaultdict(list)
for i in range(50):
n = random.randrange(4, 20, 2)
while True:
x=np.append(np.random.randint(2, 7, size=n//2), np.random.randint(7, 41, size=n//2))
if x.mean() == 12 and np.median(x) == 6.5:
break
rs[len(x)].append(x)
def random_range(n):
if n % 2:
raise AssertionError("%d must be even" % n)
r = []
while n:
i = random.randrange(4, min(20, n+1), 2)
# Don't be left with only 2 slots left.
if n - i == 2: continue
xs = random.choice(rs[i])
r.extend(xs)
n -= i
random.shuffle(r)
return r
xs = np.array(random_range(5000))
print([(i, list(xs).count(i)) for i in range(2, 41)])
print(len(xs))
print(xs.mean())
print(np.median(xs))
输出:
[(2, 620), (3, 525), (4, 440), (5, 512), (6, 403), (7, 345), (8, 126), (9, 111), (10, 78), (11, 25), (12, 48), (13, 61), (14, 117), (15, 61), (16, 62), (17, 116), (18, 49), (19, 73), (20, 88), (21, 48), (22, 68), (23, 46), (24, 75), (25, 77), (26, 49), (27, 83), (28, 61), (29, 28), (30, 59), (31, 73), (32, 51), (33, 113), (34, 72), (35, 33), (36, 51), (37, 44), (38, 25), (39, 38), (40, 46)]
5000
12.0
6.5
输出的第一行显示最终数组中有620 2,52 3,440 4等。
答案 4 :(得分:0)
尽管这篇文章已经有了一个可以接受的答案,但我想提供一种通用的非整数方法。它不需要循环或测试。这个想法是在紧凑的支持下获取PDF。以公认的Kasrâmvd答案的想法,在左右间隔中进行两个分布。选择形状参数,以使平均值降至给定值。这里有趣的机会是,人们可以创建连续的PDF,即在间隔连接处没有跳转。
作为示例,我选择了beta分布。为了在边界处具有有限的非零值,我为左边选择了beta = 1,为右边选择了alpha = 1。 查看PDF的定义和均值的要求,得出两个方程:
4.5 / alpha = 33.5 / beta
2 + 6.5 * alpha / ( alpha + 1 ) + 6.5 + 33.5 * 1 / ( 1 + beta ) = 24
这是一个二次方程式,很容易求解。刚刚使用scipy.stat.beta
的
from scipy.stats import beta
import matplotlib.pyplot as plt
import numpy as np
x1 = np.linspace(2, 6.5, 200 )
x2 = np.linspace(6.5, 40, 200 )
# i use s and t not alpha and beta
s = 1./737 *(np.sqrt(294118) - 418 )
t = 1./99 *(np.sqrt(294118) - 418 )
data1 = beta.rvs(s, 1, loc=2, scale=4.5, size=20000)
data2 = beta.rvs(1, t, loc=6.5, scale=33.5, size=20000)
data = np.concatenate( ( data1, data2 ) )
print np.mean( data1 ), 2 + 4.5 * s/(1.+s)
print np.mean( data2 ), 6.5 + 33.5/(1.+t)
print np.mean( data )
print np.median( data )
fig = plt.figure()
ax = fig.add_subplot( 1, 1, 1 )
ax.hist(data1, bins=13, density=True )
ax.hist(data2, bins=67, density=True )
ax.plot( x1, beta.pdf( x1, s, 1, loc=2, scale=4.5 ) )
ax.plot( x2, beta.pdf( x2, 1, t, loc=6.5, scale=33.5 ) )
ax.set_yscale( 'log' )
plt.show()
提供
>> 2.661366939244768 2.6495436216856976
>> 21.297348804473618 21.3504563783143
>> 11.979357871859191
>> 6.5006779033245135