从列表中获取随机样本,同时保持项目排序?


84

我有一个排序的列表,可以说:(它不仅是数字,它是使用复杂的耗时算法排序的对象的列表)

mylist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ,9  , 10 ]

是否有一些python函数可以给我N个项目,但会保持顺序?

例:

randomList = getRandom(mylist,4)
# randomList = [ 3 , 6 ,7 , 9 ]
randomList = getRandom(mylist,4)
# randomList = [ 1 , 2 , 4 , 8 ]

等等...


1
为什么不想要random.sample然后排序?
Daniel Lubarov 2011年

它采用了非平凡的算法进行排序……这不只是数字
Yochai Timmer

4
Daniel的评论有一个很小的变化:对范围进行采样[0,count),对样本进行排序(范围内的数字具有自然顺序),然​​后mylist根据索引从中提取值。使用zip可能会在稍有不同的机制下达到相同的效果。

1
好的,我可以得到答案+示例,以便我有一些要接受的东西吗?:)
Yochai Timmer

Answers:


121

以下代码将生成大小为4的随机样本:

import random

sample_size = 4
sorted_sample = [
    mylist[i] for i in sorted(random.sample(range(len(mylist)), sample_size))
]

(注意:在Python 2中,最好使用xrange代替range

说明

random.sample(range(len(mylist)), sample_size)

生成原始列表索引的随机样本。

然后对这些索引进行排序,以保留原始列表中元素的顺序。

最后,给定采样索引,列表理解会从原始列表中提取实际元素。


89

简易编码O(N + K * log(K))方式

在不替换索引的情况下抽取随机样本,对索引进行排序,然后从原始索引中获取。

indices = random.sample(range(len(myList)), K)
[myList[i] for i in sorted(indices)]

或更简而言之:

[x[1] for x in sorted(random.sample(enumerate(myList),K))]

优化的O(N)时间,O(1)-辅助空间方式

您也可以使用数学技巧,myList从左到右迭代,以动态变化的概率选择数字(N-numbersPicked)/(total-numbersVisited)。这种方法的优点是它是一种O(N)算法,因为它不涉及排序!

from __future__ import division

def orderedSampleWithoutReplacement(seq, k):
    if not 0<=k<=len(seq):
        raise ValueError('Required that 0 <= sample_size <= population_size')

    numbersPicked = 0
    for i,number in enumerate(seq):
        prob = (k-numbersPicked)/(len(seq)-i)
        if random.random() < prob:
            yield number
            numbersPicked += 1

概念验证和概率证明

在5小时内用1万亿个伪随机样本进行了仿真:

>>> Counter(
        tuple(orderedSampleWithoutReplacement([0,1,2,3], 2))
        for _ in range(10**9)
    )
Counter({
    (0, 3): 166680161, 
    (1, 2): 166672608, 
    (0, 2): 166669915, 
    (2, 3): 166667390, 
    (1, 3): 166660630, 
    (0, 1): 166649296
})

概率与真实概率的相差小于1.0001。再次运行此测试会导致顺序不同,这意味着它不会偏向一个顺序。运行与较少的样本测试[0,1,2,3,4], k=3[0,1,2,3,4,5], k=4有类似的结果。

编辑:不确定为什么人们投票反对错误的评论或害怕投票...不,这种方法没有错。=)

(这也是tegan用户在注释中的有用注释:如果这是python2,则如果您确实关心额外的空间,则将像往常一样使用xrange。)

编辑:证明:考虑选择的一个子集的均匀分布(无需更换)k从人口seq规模len(seq),我们可以在任意点考虑划分i成“左”(0,1,...,I-1)和'right'(i,i + 1,...,len(seq))。考虑到我们是numbersPicked从左边的已知子集中选取的,其余的必须来自右边的未知子集中的相同均匀分布,尽管参数现在不同。特别是,seq[i]包含选定元素的概率为#remainingToChoose/#remainingToChooseFrom,或(k-numbersPicked)/(len(seq)-i),因此我们对此进行了仿真并根据结果进行递归。(这必须终止,因为如果#remainingToChoose == #remainingToChooseFrom,则所有剩余概率均为1。)这类似于动态生成的概率树。基本上,您可以通过以先验选择为条件来模拟均匀的概率分布(随着概率树的生长,您选择当前分支的概率,使其与先验叶子相同,即以先验选择为条件;这将起作用,因为该概率统一精确地为N / k)。

编辑:蒂莫西·希尔兹(Timothy Shields)提到了“水库采样”,这是在len(seq)未知(例如带有生成器表达式)时此方法的推广。具体来说,如果在原位完成,则称为“算法R”的是O(N)和O(1)空间。它涉及获取第一个N元素并慢慢替换它们(也给出了归纳证明的提示)。在Wikipedia页面上还可以找到有用的分布式采样和其他类型的储层采样。

编辑:这是下面一种在语义上更明显的方式对其进行编码的方法。

from __future__ import division
import random

def orderedSampleWithoutReplacement(seq, sampleSize):
    totalElems = len(seq)
    if not 0<=sampleSize<=totalElems:
        raise ValueError('Required that 0 <= sample_size <= population_size')

    picksRemaining = sampleSize
    for elemsSeen,element in enumerate(seq):
        elemsRemaining = totalElems - elemsSeen
        prob = picksRemaining/elemsRemaining
        if random.random() < prob:
            yield element
            picksRemaining -= 1

from collections import Counter         
Counter(
    tuple(orderedSampleWithoutReplacement([0,1,2,3], 2))
    for _ in range(10**5)


1
@pst:没有缺点,只是一个加速O(N),而O(N log(N))
ninjagecko

1
非常好,我也想知道如何执行此线性方法。此公式是否有维基百科页面?:)
Jochen Ritzel 2011年

2
我很惊讶这个答案没有更多赞誉,它实际上解释了该解决方案的工作方式(并提供了另一个解决方案!),而不是第一个答案(仅是一行代码)-我不知道为什么还是如何运作。
crazy2be 2011年

1
尼斯解决方案ninjagecko。如果有人有兴趣编写解决方案,那么您的解决方案就有一个很好的归纳证明。
尼尔·G,

3
不错的解决方案!不要忘记添加from __future__ import division为那些运行Python的2
xApple

7

也许您可以只生成索引样本,然后从列表中收集项目。

randIndex = random.sample(range(len(mylist)), sample_size)
randIndex.sort()
rand = [mylist[i] for i in randIndex]

4

显然random.sample是在python 2.3中引入的

因此,对于该版本,我们可以使用随机播放(例如4个项目):

myRange =  range(0,len(mylist)) 
shuffle(myRange)
coupons = [ bestCoupons[i] for i in sorted(myRange[:4]) ]

4
您使用的是Python 2.2吗?您应该升级...这已经过时了。
卡特里尔2011年

1
好吧,这是我们在服务器上拥有的。.在系统范围内进行更新太多官僚
作风

-2

random.sample实现它。

>>> random.sample([1, 2, 3, 4, 5],  3)   # Three samples without replacement
[4, 1, 5]

9
没有命令。
阿斯特丽德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.