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

Get random sample from list while maintaining ordering of items?

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

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

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

例子:

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

等。。。


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

1
2
3
4
5
6
import random

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

(注意:对于python 2,最好使用xrange而不是range)。

解释

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

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

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

最后,在给定抽样索引的情况下,列表理解从原始列表中提取实际元素。


简单到编码O(n+k*log(k))方式

随机抽取一个不替换索引的样本,对索引进行排序,并从原始索引中提取它们。

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

或者更简明扼要地说:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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万亿个伪随机样本进行模拟:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 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。)

编辑:证据:考虑到从len(seq)大小的群体seq中选择k的子集的均匀分布(不替换),我们可以考虑将i的任意点分为‘左’(0,1,…,i-1)和‘右’(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元素并缓慢地替换它们(也给出了归纳证明的提示)。维基百科页面还提供了有用的分布式变量和各种各样的水库采样变量。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
2
3
randIndex = random.sample(range(len(mylist)), sample_size)
randIndex.sort()
rand = [mylist[i] for i in randIndex]

显然,在python 2.3中引入了random.sample

所以对于下面的版本,我们可以使用shuffle(例如4个项目):

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


随机抽样实施。

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