Làm cách nào để lấy mẫu từ cho biến ngẫu nhiên, mỗi biến có các hàm khối lượng khác nhau, trong R?


8

Trong R, tôi có một N×K ma trận P nơi i 'th dãy P tương ứng với một phân phối trên {1,...,K} . Về cơ bản, tôi cần lấy mẫu từ mỗi hàng một cách hiệu quả. Một thực hiện ngây thơ là:

X = rep(0, N);
for(i in 1:N){
    X[i] = sample(1:K, 1, prob = P[i, ]);
}

Điều này là quá chậm. Về nguyên tắc tôi có thể chuyển cái này sang C nhưng tôi chắc chắn phải có một cách hiện có để làm việc này. Tôi muốn một cái gì đó theo tinh thần của mã sau đây (không hoạt động):

X = sample(1:K, N, replace = TRUE, prob = P)

EDIT: Để có động lực, lấy N=10000K=100 . Tôi có ma trận P1,...,P5000 tất cả N×K và tôi cần lấy mẫu một vectơ từ mỗi ma trận .


Vì vậy, bạn muốn một mẫu kích thước 1 từ phân phối xác suất của mỗi hàng?
Đức hồng y

@cardinal Đúng vậy.
anh chàng

Tôi sẽ quan tâm để biết kích thước vấn đề bạn đang xem xét. (Đó là, giá trị tiêu biểu của và trong trường hợp của bạn là gì?)KNK
hồng y

1
100 N 10000 5000 20000K là cho tất cả ý định và mục đích. đang ngồi khoảng . Quá trình này đang được lặp lại ở bất cứ đâu từ đến lần. 100N10000500020000
anh chàng

1
@whuber Có; những gì tôi đưa vào thực hiện ngây thơ của tôi là chính xác những gì cần phải được thực hiện.
anh chàng

Câu trả lời:


12

Chúng ta có thể làm điều này theo một vài cách đơn giản . Đầu tiên là dễ viết mã, dễ hiểu và nhanh chóng hợp lý. Thứ hai là một phức tạp hơn chút, nhưng nhiều hiệu quả hơn đối với kích thước của vấn đề này so với phương pháp đầu tiên hoặc các phương pháp khác được đề cập ở đây.

Cách 1 : Nhanh và bẩn.

Để có được một quan sát duy nhất từ ​​phân phối xác suất của mỗi hàng, chúng ta chỉ cần làm như sau.

# Q is the cumulative distribution of each row.
Q <- t(apply(P,1,cumsum))

# Get a sample with one observation from the distribution of each row.
X <- rowSums(runif(N) > Q) + 1

Điều này tạo ra phân phối tích lũy của từng hàng và sau đó lấy mẫu một quan sát từ mỗi phân phối. Lưu ý rằng nếu chúng ta có thể sử dụng lại thì chúng ta có thể tính một lần và lưu trữ để sử dụng sau. Tuy nhiên, câu hỏi cần một cái gì đó hoạt động cho một khác nhau ở mỗi lần lặp.P Q PP PQP

Nếu bạn cần nhiều ( ) quan sát từ mỗi hàng, sau đó thay thế dòng cuối cùng bằng hàng sau.n

# Returns an N x n matrix
X <- replicate(n, rowSums(runif(N) > Q)+1)

Điều này thực sự không phải là một cách cực kỳ hiệu quả nói chung để làm điều này, nhưng nó không tận dụng tốt Rkhả năng vector hóa, mà thường là yếu tố chính của tốc độ thực hiện. Nó cũng đơn giản để hiểu.

Cách 2 : Ghép các cdf.

Giả sử chúng ta có một hàm lấy hai vectơ, vectơ thứ hai được sắp xếp theo thứ tự không tăng đơn điệu và tìm thấy chỉ số trong vectơ thứ hai của giới hạn dưới lớn nhất của mỗi phần tử trong phần tử thứ nhất. Sau đó, chúng ta có thể sử dụng hàm này và một mẹo nhỏ: Chỉ cần tạo tổng tích lũy của các cdf của tất cả các hàng. Điều này mang lại một vectơ tăng đơn điệu với các phần tử trong phạm vi .[0,N]

Đây là mã.

i <- 0:(N-1)

# Cumulative function of the cdfs of each row of P.
Q <- cumsum(t(P))

# Find the interval and then back adjust
findInterval(runif(N)+i, Q)-i*K+1

Lưu ý dòng cuối cùng làm gì, nó tạo ra các biến ngẫu nhiên được phân phối trong và sau đó gọi để tìm chỉ số của giới hạn dưới lớn nhất của mỗi mục . Vì vậy, điều này cho chúng ta biết rằng phần tử đầu tiên sẽ được tìm thấy giữa chỉ số 1 và chỉ số , phần tử thứ hai sẽ được tìm thấy giữa chỉ số và , v.v., mỗi phần theo phân phối của hàng tương ứng . Sau đó, chúng ta cần quay lại biến đổi để đưa từng chỉ số trở lại trong phạm vi .K K + 1 2 K P { 1 , Rời , K }(0,1),(1,2),,(N1,N)findIntervalrunif(N)+iKK+12KP{1,,K}

findIntervalnhanh về cả thuật toán và triển khai, nên phương pháp này cực kỳ hiệu quả.

Điểm chuẩn

Trên máy tính xách tay cũ của tôi (MacBook Pro, 2,66 GHz, RAM 8GB), tôi đã thử điều này với và và tạo 5000 mẫu có kích thước , chính xác như được đề xuất trong câu hỏi cập nhật, với tổng số 50 triệu biến thể ngẫu nhiên .N=10000NK=100N

Mã cho Phương pháp 1 mất gần 15 phút để thực thi hoặc khoảng 55K biến thiên ngẫu nhiên mỗi giây. Mã cho Phương pháp 2 mất khoảng bốn phút rưỡi để thực thi, hoặc khoảng 183K biến thiên ngẫu nhiên mỗi giây.

Đây là mã cho mục đích tái sản xuất. (Lưu ý rằng, như được chỉ ra trong một nhận xét, được tính toán lại cho mỗi trong số 5000 lần lặp để mô phỏng tình huống của OP.)Q

# Benchmark code
N <- 10000
K <- 100

set.seed(17)
P <- matrix(runif(N*K),N,K)
P <- P / rowSums(P)

method.one <- function(P)
{
    Q <- t(apply(P,1,cumsum))
    X <- rowSums(runif(nrow(P)) > Q) + 1
}

method.two <- function(P)
{
    n <- nrow(P)
    i <- 0:(n-1)
    Q <- cumsum(t(P))
    findInterval(runif(n)+i, Q)-i*ncol(P)+1
}

Đây là đầu ra.

# Method 1: Timing
> system.time(replicate(5e3, method.one(P)))
   user  system elapsed 
691.693 195.812 899.246 

# Method 2: Timing
> system.time(replicate(5e3, method.two(P)))
   user  system elapsed 
182.325  82.430 273.021 

Postcript : Bằng cách nhìn vào mã cho findInterval, chúng ta có thể thấy rằng nó thực hiện một số kiểm tra trên đầu vào để xem nếu có NAmục hoặc nếu đối số thứ hai không được sắp xếp. Do đó, nếu chúng tôi muốn đạt được hiệu suất cao hơn trong số này, chúng tôi có thể tạo phiên bản sửa đổi của riêng mình findIntervalđể loại bỏ các kiểm tra không cần thiết trong trường hợp của chúng tôi.


Tôi sẽ cho nó một shot. Tôi nghĩ rằng điều này là quá chậm vì việc sử dụng "áp dụng" mà tôi nghĩ là đang ẩn một vòng lặp trong R. Thứ tự cường độ của và nằm trong ví dụ của bạn, nhưng nó nằm trong triển khai MCMC. KNK
anh chàng

Mã ở trên không giả sử rằng tất cả (nghiêm ngặt). Pij>0
Đức hồng y

@guy: chỉ cần được tính một lần khi bắt đầu và được lưu trữ. Q
Đức hồng y

Thật không may thay đổi qua mỗi lần lặp. P
anh chàng

1
Cách 2 khá thông minh. Cảm ơn :) Tôi nghĩ rằng nó hoạt động đủ tốt trong giai đoạn này của công việc của tôi.
anh chàng

6

Một forvòng lặp có thể rất chậm trong R. Làm thế nào về vector hóa đơn giản này với sapply?

n <- 10000
k <- 200

S <- 1:k
p <- matrix(rep(1 / k, n * k), nrow = n, ncol = k)
x <- numeric(n)

x <- sapply(1:n, function(i) sample(S, 1, prob = p[i,]))

Tất nhiên, p đồng phục này chỉ để thử nghiệm.


Tôi đã thay đổi thành để so sánh công bằng hơn và nhân rộng hai dòng cuối cùng 500 lần. Nó chạy trong 100 giây trên máy tính xách tay của tôi, hoặc khoảng 10/9 thời gian của mã trong câu trả lời khác. Điều đó khá tương đương. Điều thú vị là mã của bạn sử dụng hầu hết thời gian "người dùng", trong khi mã trong câu trả lời của tôi sử dụng tỷ lệ thời gian "hệ thống" lớn hơn nhiều. Tôi không chắc tại thời điểm đó. Ngoài ra, tôi không chắc chắn, nếu có, hiệu ứng mô phỏng bằng cách sử dụng đồng phục trong trường hợp của bạn có thể có. k=100
Đức hồng y

Tái tạo dòng áp chót sẽ khiến R phân bổ bộ nhớ cho x nhiều lần và tôi tin rằng điều đó rất chậm. Bạn có thể cố gắng sao chép chỉ dòng cuối cùng, hồng y? Điều này "người dùng" chống lại "hệ thống" thời gian là buồn cười.
Zen

Tôi đã thử với giống như trong mã của tôi. Tôi nhận được 121 giây cho 500 lần lặp. Vì vậy, có một bộ đồng phục dường như có vấn đề một chút. Ở mức độ nào, tôi thực sự hơi ngạc nhiên khi phương pháp này cạnh tranh như nó. (+1)P
Đức hồng y

Hài hước lắm, loại bỏ dòng đó không ảnh hưởng đến thời gian. Một chút ngạc nhiên.
Đức hồng y

OMG, R là hành vi đôi khi không thể đoán trước ...
Zen
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.