アイテムの順序を維持しながら、リストからランダムサンプルを取得しますか?


84

私はソートされたリストを持っています、例えば:(それは実際には数字だけではなく、複雑で時間のかかるアルゴリズムでソートされたオブジェクトのリストです)

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

N個のアイテムを取得するが順序を維持するPython関数はありますか?

例:

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

等...


1
random.sample並べ替えてみませんか?
ダニエルルバロフ2011年

それは重要なアルゴリズムでソートされています...それは実際には数字だけではありません
Yochai Timmer 2011年

4
ダニエルのコメントへのごくわずかな変更:範囲をサンプリングし[0,count)、サンプルを並べ替えて(範囲内の数値は自然順序になっています)、mylistインデックスに基づいて値を抽出します。を使用zipすると、わずかに異なるメカニズムで同じ効果を達成できます。

1
わかりました、答えと例を取得できるので、受け入れるものがありますか?:)
Yochai Timmer 2011年

回答:


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の因数で異なります。このテストを再度実行すると、順序が異なります。つまり、1つの順序に偏っていません。より少ないサンプルでテストを実行する[0,1,2,3,4], k=3と、[0,1,2,3,4,5], k=4同様の結果が得られました。

編集:なぜ人々が間違ったコメントに投票したり、投票することを恐れているのかわからない...いいえ、この方法に問題はありません。=)

(コメントのユーザーteganからの有用なメモ:これがpython2の場合、余分なスペースが本当に気になるのであれば、いつものようにxrangeを使用することをお勧めします。)

編集:証明:サイズのk母集団からサブセットを選択する(置換なしの)一様分布を考慮すると、任意のポイントでの「左」(0,1、...、i-1)へのパーティションを考慮することができます。および「右」(i、i + 1、...、len(seq))。左側の既知のサブセットから選択したことを考えると、パラメーターは異なるものの、残りは右側の未知のサブセットの同じ一様分布に由来する必要があります。特に、選択された要素を含む確率は、、またはseqlen(seq)inumbersPickedseq[i]#remainingToChoose/#remainingToChooseFrom(k-numbersPicked)/(len(seq)-i)、それをシミュレートし、結果を繰り返します。(#remainingToChoose == #remainingToChooseFromの場合、残りのすべての確率は1であるため、これは終了する必要があります。)これは、動的に生成される確率ツリーに似ています。基本的に、事前の選択を条件として均一な確率分布をシミュレートできます(確率ツリーを成長させるときに、現在のブランチの確率を、以前の葉と同じように後方に選択します。つまり、以前の選択を条件とします。これは、次の理由で機能します。この確率は一様に正確にN / kです)。

編集:Timothy Shieldsは、Reservoir Samplingについて言及しています。これは、この方法を一般化したものです。len(seq)不明な(ジェネレーター式など)。具体的には、「アルゴリズムR」として示されているのは、インプレースで実行された場合のO(N)およびO(1)スペースです。それは最初のN要素を取り、それらをゆっくりと置き換えることを含みます(帰納的証明のヒントも与えられます)。ウィキペディアのページには、貯水池サンプリングの便利な分散バリアントやその他のバリアントもあります。

編集:より意味的に明白な方法でそれを以下にコーディングする別の方法があります。

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 2011年

1
とてもいいです、私もこの線形アプローチをどのように行うのか疑問に思いました。この数式にはウィキペディアのページがありますか?:)
Jochen Ritzel 2011年

2
この回答に賛成票が多くないことに驚いています。最初の回答は1行のスニペットであるのに対し、実際にはソリューションがどのように機能するかを説明しています(そして別のソリューションを提供します!)。理由がわからない、またはそれがどのように機能したか。
crazy2be 2011年

1
素晴らしい解決策ninjagecko。誰かがそれを書くことに興味があるなら、あなたの解決策に素晴らしい帰納法の証拠があります。
ニールG

3
素晴らしい解決策!追加することを忘れないでくださいfrom __future__ import divisionPythonの2を実行している人のため
xApple

7

インデックスのサンプルを生成して、リストからアイテムを収集できるかもしれません。

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

4

どうやら random.sampleで導入されました

その下のバージョンでは、シャッフルを使用できます(4項目の例):

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

4
Python 2.2を使用していますか?!アップグレードする必要があります...それは時代遅れです。
カトリエル2011年

1
まあ、それは私たちがサーバー上に持っているものです..システム全体の更新を行うことはあまりにも多くの官僚主義
Yochai Timmer 2011年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.