Lấy mẫu ngẫu nhiên từ danh sách trong khi duy trì thứ tự các mặt hàng?


84

Tôi có một danh sách được sắp xếp, giả sử: (nó không thực sự chỉ là những con số, nó là danh sách các đối tượng được sắp xếp với một thuật toán phức tạp tốn thời gian)

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

Có một số chức năng python sẽ cung cấp cho tôi N trong số các mặt hàng, nhưng sẽ giữ thứ tự không?

Thí dụ:

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

Vân vân...


1
Tại sao bạn không muốn random.samplevà sau đó sắp xếp?
Daniel Lubarov

Nó được sắp xếp với một thuật toán không tầm thường ... nó không phải là thực sự chỉ là con số
Yochai Timmer

4
Một thay đổi rất nhỏ đối với nhận xét của Daniel: lấy mẫu một dải [0,count), sắp xếp mẫu (các số trong dải có thứ tự tự nhiên), sau đó trích xuất các giá trị mylistdựa trên các chỉ số. Sử dụng zipcó thể đạt được hiệu quả tương tự với các cơ chế khác nhau một chút.

1
ok, tôi có thể nhận được một câu trả lời + ví dụ để tôi có một cái gì đó để chấp nhận? :)
Yochai Timmer

Câu trả lời:


121

Mã sau sẽ tạo một mẫu ngẫu nhiên có kích thước 4:

import random

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

(lưu ý: với Python 2, sử dụng tốt hơn xrangethay vì range)

Giải trình

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

tạo một mẫu ngẫu nhiên của các chỉ số của danh sách ban đầu.

Các chỉ số này sau đó được sắp xếp để bảo toàn thứ tự của các phần tử trong danh sách ban đầu.

Cuối cùng, việc hiểu danh sách lấy ra các yếu tố thực tế từ danh sách ban đầu, với các chỉ số được lấy mẫu.


89

Cách đơn giản để mã O (N + K * log (K))

Lấy một mẫu ngẫu nhiên mà không cần thay thế các chỉ số, sắp xếp các chỉ số và lấy chúng từ bản gốc.

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

Hay ngắn gọn hơn:

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

Tối ưu hóa O (N) - thời gian, O (1) - không gian phụ

Ngoài ra, bạn có thể sử dụng một mẹo toán học và lặp đi lặp lại myListtừ trái sang phải, chọn các số với xác suất thay đổi động (N-numbersPicked)/(total-numbersVisited). Ưu điểm của cách tiếp cận này là nó là một O(N)thuật toán vì nó không liên quan đến việc sắp xếp!

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

Chứng minh khái niệm và kiểm tra rằng các xác suất là đúng :

Được mô phỏng với 1 nghìn tỷ mẫu giả ngẫu nhiên trong suốt 5 giờ:

>>> 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
})

Xác suất phân biệt với xác suất thực nhỏ hơn 1.0001. Chạy thử nghiệm này một lần nữa dẫn đến một thứ tự khác có nghĩa là nó không thiên về một thứ tự. Chạy thử nghiệm với ít mẫu hơn [0,1,2,3,4], k=3[0,1,2,3,4,5], k=4có kết quả tương tự.

chỉnh sửa: Không chắc tại sao mọi người lại bình chọn sai hoặc sợ ủng hộ ... KHÔNG, không có gì sai với phương pháp này. =)

(Cũng có một lưu ý hữu ích từ người dùng tegan trong phần nhận xét: Nếu đây là python2, bạn sẽ muốn sử dụng xrange, như thường lệ, nếu bạn thực sự quan tâm đến dung lượng bổ sung.)

chỉnh sửa : Chứng minh: Xét phân bố đồng đều (không có thay thế) của chọn một tập hợp con của ktrên một dân seqquy mô len(seq), chúng ta có thể xem xét một phân vùng tại một điểm tùy ý ivào 'trái' (0,1, ..., i-1) và 'right' (i, i + 1, ..., len (seq)). Giả sử rằng chúng tôi đã chọn numbersPickedtừ tập hợp con đã biết bên trái, phần còn lại phải đến từ cùng một phân phối đồng đều trên tập hợp con chưa biết bên phải, mặc dù các tham số bây giờ khác nhau. Cụ thể, xác suất seq[i]có chứa một phần tử đã chọn là #remainingToChoose/#remainingToChooseFrom, hoặc(k-numbersPicked)/(len(seq)-i), vì vậy chúng tôi mô phỏng điều đó và lặp lại kết quả. (Điều này phải chấm dứt vì nếu #remainingToChoose == #remainingToChooseFrom, thì tất cả các xác suất còn lại là 1.) Điều này tương tự như một cây xác suất xảy ra được tạo động. Về cơ bản, bạn có thể mô phỏng phân bố xác suất đồng nhất bằng cách điều chỉnh các lựa chọn trước (khi bạn trồng cây xác suất, bạn chọn xác suất của nhánh hiện tại sao cho nó là aposteriori giống với các lá trước đó, tức là được điều chỉnh trên các lựa chọn trước; điều này sẽ hoạt động vì xác suất này đồng nhất chính xác N / k).

sửa : Timothy Shields đề cập đến Lấy mẫu hồ chứa , đó là khái quát của phương pháp này khi len(seq)chưa biết (chẳng hạn như với biểu thức máy phát). Cụ thể, thuật toán được lưu ý là "thuật toán R" là không gian O (N) và O (1) nếu được thực hiện tại chỗ; nó liên quan đến việc lấy N phần tử đầu tiên và từ từ thay thế chúng (một gợi ý về một bằng chứng quy nạp cũng được đưa ra). Ngoài ra còn có các biến thể phân tán hữu ích và các biến thể khác của lấy mẫu hồ chứa được tìm thấy trên trang wikipedia.

chỉnh sửa : Đây là một cách khác để viết mã bên dưới theo cách rõ ràng hơn về mặt ngữ nghĩa.

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: không có nhược điểm, chỉ cần một sự tăng tốc của O(N)kháO(N log(N))
ninjagecko

1
Rất hay, tôi cũng đang tự hỏi làm cách nào để thực hiện phương pháp tiếp cận tuyến tính này. Công thức này có trang wikipedia không? :)
Jochen Ritzel

2
Tôi ngạc nhiên vì câu trả lời này không có nhiều lượt ủng hộ hơn, nó thực sự giải thích cách giải pháp hoạt động (và cung cấp một giải pháp khác!), Trái ngược với câu trả lời đầu tiên chỉ là một đoạn mã một dòng - khiến tôi không biết tại sao hoặc nó hoạt động như thế nào.
crazy2be

1
Giải pháp tốt đẹp ninjagecko. Có một bằng chứng quy nạp tuyệt vời cho giải pháp của bạn nếu bất kỳ ai quan tâm đến việc viết nó lên.
Neil G

3
Giải pháp tốt! Đừng quên bổ sung from __future__ import divisioncho những người đang chạy Python 2.
xApple

7

Có thể bạn chỉ có thể tạo mẫu chỉ số và sau đó thu thập các mục từ danh sách của mình.

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

4

Rõ ràng random.sampleđã được giới thiệu trong python 2.3

vì vậy đối với phiên bản dưới đó, chúng tôi có thể sử dụng xáo trộn (ví dụ cho 4 mục):

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

4
Bạn đang sử dụng Python 2.2 ?! Bạn nên nâng cấp ... đó là cách lỗi thời.
Katriel

1
tốt, nó những gì chúng tôi có trên các máy chủ .. làm cho một bản cập nhật cho toàn hệ thống là quá nhiều quan liêu
Yochai Timmer

-2

random.sample thực hiện nó.

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

9
Điều đó không được đặt hàng.
Astrid
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.