Python中的random.randint和random.choice:使用相同值会产生不同的结果。

7
我让我的学生们编写一个Python程序,将一对6面骰子的100次投掷结果存储在列表中,并绘制成直方图。
起初我认为random.choice(1,2,3,4,5,6)不如random.randint(1,6),但后来我注意到使用random.choice的学生的直方图更好地反映了预期的结果。例如,在几乎所有使用random.randint(1,6)的学生的直方图中,12(6+6)的出现率异常高。有人知道发生了什么吗?

请阅读此内容:https://dev59.com/R10b5IYBdhLWcg3wIeIw - MooingRawr
发生的情况是你“注意到”了偶然波动,并将它们归因于比实际情况更多的东西。 - John Coleman
@JohnColeman:如果那是真的,那种偏斜就会是随机的,不是吗?但事实并非如此,这已经持续多年进行了许多次试验。 - N. Duncan
3个回答

6

来源文档

几乎所有模块函数都依赖于基本函数 random(),它会生成一个随机浮点数,范围在半开区间 [0.0, 1.0) 内。Python 使用 Mersenne Twister 作为其核心生成器。它能够产生精度达到 53 位的浮点数,并且具有 2**19937-1 的周期。底层的 C 实现既快速又线程安全。Mersenne Twister 是目前为止使用最广泛的随机数生成器之一。然而,由于完全可确定性,它并不适用于所有目的,并且对于加密目的来说完全不适用。

因此,在结果方面不应该有任何真正的差异。但是,我不同意 random.choice()randint() 差,实际上,随机选择在生成随机数方面更快。当查看源代码时:

def randint(self, a, b):
    return self.randrange(a, b+1)

def randrange(self, start, stop=None, step=1, _int=int, _maxwidth=1L<<BPF):
    istart = _int(start)
    if istart != start:
        # not executed
    if stop is None:
        # not executed

    istop = _int(stop)
    if istop != stop:
        # not executed
    width = istop - istart
    if step == 1 and width > 0:
        if width >= _maxwidth:
            # not executed
        return _int(istart + _int(self.random()*width))

而对于choice()

def choice(self, seq):
    return seq[int(self.random() * len(seq))]

你可以看到randint()使用了额外的开销,即使用了randrange() 编辑:正如@abarnert在评论中指出的那样,这里实际上几乎没有性能差异,而randint(1,6)是表示掷骰子的一种明确且直观的方式。
我对两者进行了10000次投掷的比较,并没有看到任何偏差,因此有可能你的输入样本太小了。

enter image description here

这里是掷一次骰子两次的分布,同样非常均匀:

enter image description here

我从这两个有用的回答中借鉴了一些内容:选择性能 vs randintPython的random.randint是否具有统计随机性?,这些都是进一步阅读的好资料。


1
使用randrange会增加一些额外的开销,一个函数调用并不是免费的,但与正在进行的工作相比,这可能不足以产生差异。无论如何,如果真的很重要,你应该进行测试而不是猜测。从我笔记本电脑上的快速测试来看,choice(range(1, 6))需要1070纳秒,choice((1,2,3,4,5,6))需要786纳秒,randint(1,6)需要1099纳秒,而randrange(1, 7)需要1070纳秒。所以,看起来额外的开销足够小,可以在噪音中忽略不计,但choice由于其他原因稍微更快一些——只要你有一个元组可以提供给它。 - abarnert
是的,对于较大的输入,时间差似乎并不会增长,保持相对较小。 - user3483203
然而,这些差异从未超过30%,每个骰子额外滚动30ns在实际问题中不太重要,因此更重要的问题是哪种方法对于给定的应用程序更清晰。我认为randint(1,6)是实现1d6掷骰子最明显的方法,即使它比在常量元组上的randrange(1,7)choice稍慢。 - abarnert
leetcode.com上编程时,random.random()random.randint()快得多,因为运行时间只有约300-500毫秒。 - Duke

2

您说得对,您在学生的直方图中观察到的12的数量比掷出12的理论概率要高,但原因并非您所想。

一个实验:

import random

def roll_dice(method):
    if method == "choice":
        return random.choice([1,2,3,4,5,6]) + random.choice([1,2,3,4,5,6])
    else:
        return random.randint(1,6) + random.randint(1,6)

def est_prob(n,k,method):
    rolls = [roll_dice(method) for _ in range(k)]
    return rolls.count(n)/k

def test12(n,k,method):
    return sum(1 if est_prob(12,n,method) > 1/36 else 0 for _ in range(k))/k

请注意,test12(100,10000,"randint")估计基于randint的100个骰子掷出的直方图过度代表了和为12的概率。

典型运行:

>>> test12(100,10000,"randint")
0.5288

这个结果比50%大得多,而且在统计学上具有显著性(进行10000次试验是估算概率的相当大的次数)。

那么,randint()存在偏差的证据呢?不要急:

>>> test12(100,10000,"choice")
0.5342

使用random.choice()同样如此。这并不令人惊讶,因为基于100次掷骰子的大部分骰子掷出直方图都会高估12的概率。
当你掷100次骰子时,预期得到的总点数为12的次数是100/36 = 2.78次。但是--你只能观察到整数个12。观察到3个或更多12的概率(从而导致过度表现12的直方图)是P(X >= 3),其中X是具有参数p = 1/36和n = 100的二项式随机变量。可以计算出这个概率。
P(X >= 3) = 1 - P(X<=2) 
          = 1 - P(0) - P(1) - P(2)
          = 1 - 0.0598 - 0.1708 - 0.2416
          = 0.5278

因此,约有53%的这样的直方图有“过多”的12,这是您在使用random.choice()random.randint()时都会看到的现象。
似乎您更多地注意到了randint的情况,并将其解释为偏差(尽管它并不是),并假设这是randint的缺陷。

0

在Python中生成随机整数的最快方法实际上让我感到惊讶:

import random

die = int(random.random() * 6) + 1  # The equivalent to 'die = random.randint(1, 6)

仅仅看起来,使用int(random.random)方法计算似乎更加复杂,但速度差异相当明显。

我在MONTY算法中测试了两种方法,使用int(random.random)方法的速度提高了约200%。

也比random.choice快得多。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接