2016-05-05 21 views
8

Tôi có một lớn, rộng data.table (hàng 20m) được khóa bằng ID người nhưng có nhiều cột (~ 150) có nhiều giá trị null. Mỗi cột là một trạng thái/thuộc tính được ghi lại mà tôi muốn chuyển tiếp cho mỗi người. Mỗi người có thể có từ 10 đến 10.000 quan sát và có khoảng 500.000 người trong bộ này. Giá trị từ một người không thể 'chảy máu' vào người sau, vì vậy giải pháp của tôi phải tôn trọng cột ID và nhóm người phù hợp.locf hiệu quả theo nhóm trong một dữ liệu R. Có thể

Đối với mục đích trình diễn - đây là một đầu vào mẫu rất nhỏ:

DT = data.table(
    id=c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3), 
    aa=c("A", NA, "B", "C", NA, NA, "D", "E", "F", NA, NA, NA), 
    bb=c(NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA), 
    cc=c(1, NA, NA, NA, NA, 4, NA, 5, 6, NA, 7, NA) 
) 

Nó trông như thế này:

id aa bb cc 
1: 1 A NA 1 
2: 1 NA NA NA 
3: 1 B NA NA 
4: 1 C NA NA 
5: 2 NA NA NA 
6: 2 NA NA 4 
7: 2 D NA NA 
8: 2 E NA 5 
9: 3 F NA 6 
10: 3 NA NA NA 
11: 3 NA NA 7 
12: 3 NA NA NA 

sản lượng dự kiến ​​của tôi trông như thế này:

id aa bb cc 
1: 1 A NA 1 
2: 1 A NA 1 
3: 1 B NA 1 
4: 1 C NA 1 
5: 2 NA NA NA 
6: 2 NA NA 4 
7: 2 D NA 4 
8: 2 E NA 5 
9: 3 F NA 6 
10: 3 F NA 6 
11: 3 F NA 7 
12: 3 F NA 7 

tôi đã tìm thấy một giải pháp data.table hoạt động, nhưng nó rất chậm trên các tập dữ liệu lớn của tôi:

DT[, na.locf(.SD, na.rm=FALSE), by=id] 

Tôi đã tìm thấy các giải pháp tương đương bằng cách sử dụng dplyr cũng chậm như nhau.

GRP = DT %>% group_by(id) 
data.table(GRP %>% mutate_each(funs(blah=na.locf(., na.rm=FALSE)))) 

Tôi đã hy vọng rằng tôi có thể đưa ra một 'tự' cán tham gia sử dụng các chức năng data.table, nhưng tôi chỉ dường như không thể làm cho nó đúng (tôi nghi ngờ tôi sẽ cần phải sử dụng .N nhưng tôi chỉ đã không tìm ra nó).

Tại thời điểm này, tôi nghĩ tôi sẽ phải viết một cái gì đó trong Rcpp để áp dụng hiệu quả locf nhóm.

Tôi mới sử dụng R, nhưng tôi không quen với C++ - vì vậy tôi tự tin mình có thể làm được. Tôi chỉ cảm thấy như có một cách hiệu quả để làm điều này trong R sử dụng data.table.

+0

Tôi khá chắc chắn 'DT [, lapply (.SD, na.locf, F), bởi = id] 'sẽ nhanh hơn – eddi

+0

Tôi thực sự bắt đầu với điều đó và thấy hiệu suất tồi tệ hơn. –

+0

Rolling self join có vẻ là điểm ở đây, tôi nhớ một số câu hỏi có cả hai 'na.locf' và rolling join answers, vì vậy tôi nghĩ bạn có thể tìm thấy câu trả lời trong cơ sở kiến ​​thức SO hiện tại. – jangorecki

Trả lời

14

Một rất đơn giản na.locf thể được xây dựng bằng cách chuyển tiếp (cummax) các phi NA chỉ số ((!is.na(x)) * seq_along(x)) và Subsetting phù hợp:

x = c(1, NA, NA, 6, 4, 5, 4, NA, NA, 2) 
x[cummax((!is.na(x)) * seq_along(x))] 
# [1] 1 1 1 6 4 5 4 4 4 2 

này tái tạo na.locf với một cuộc tranh cãi na.rm = TRUE, để có được na.rm = FALSE hành vi chúng ta chỉ cần để đảm bảo phần tử đầu tiên trong số cummaxTRUE:

x = c(NA, NA, 1, NA, 2) 
x[cummax(c(TRUE, tail((!is.na(x)) * seq_along(x), -1)))] 
#[1] NA NA 1 1 2 

Trong trường hợp này, chúng ta cần phải đưa vào tài khoản không chỉ là NA chỉ số phi nhưng, cũng, của các chỉ số nơi (có trật tự, hoặc để được đặt hàng) "id" cột thay đổi giá trị:

id = c(10, 10, 11, 11, 11, 12, 12, 12, 13, 13) 
c(TRUE, id[-1] != id[-length(id)]) 
# [1] TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE 

Kết hợp ở trên:

id = c(10, 10, 11, 11, 11, 12, 12, 12, 13, 13) 
x = c(1, NA, NA, 6, 4, 5, 4, NA, NA, 2) 

x[cummax(((!is.na(x)) | c(TRUE, id[-1] != id[-length(id)])) * seq_along(x))] 
# [1] 1 1 NA 6 4 5 4 4 NA 2 

Note, mà ở đây chúng tôi OR phần tử đầu tiên với TRUE, tức là làm cho nó tương đương với TRUE, do đó nhận được hành vi na.rm = FALSE.

Và ví dụ này:

id_change = DT[, c(TRUE, id[-1] != id[-.N])] 
DT[, lapply(.SD, function(x) x[cummax(((!is.na(x)) | id_change) * .I)])] 
# id aa bb cc 
# 1: 1 A NA 1 
# 2: 1 A NA 1 
# 3: 1 B NA 1 
# 4: 1 C NA 1 
# 5: 2 NA NA NA 
# 6: 2 NA NA 4 
# 7: 2 D NA 4 
# 8: 2 E NA 5 
# 9: 3 F NA 6 
#10: 3 F NA 6 
#11: 3 F NA 7 
#12: 3 F NA 7 
+5

downvote là rất không rõ ràng với tôi, và một số lời giải thích sẽ được đánh giá cao – eddi

+1

Câu trả lời tuyệt vời imo - không chỉ là một phiên bản nhanh hơn nhiều của một 'na.locf' thường xuyên, nhưng nó cũng bổ sung thêm một sửa đổi để làm điều đó cho mỗi nhóm (giả sử các nhóm được sắp xếp), ** không có ** thực sự làm một vòng lặp 'by' (sẽ giới thiệu thêm' eval' cho mỗi nhóm và sẽ làm chậm nó xuống). Trừ khi tôi đang thiếu một cái gì đó - điều này nên được thực hiện tiêu chuẩn 'na.locf', thay vì các công cụ' rle' mà 'zoo' làm. – eddi

+0

@eddi: cảm ơn bạn đã chỉnh sửa. Tôi đoán 'zoo :: na.locf' linh hoạt hơn, tuy nhiên, tôi tin rằng đối với các trường hợp đơn giản, bản quét' 4-5 * length (x) 'của phiên bản' cummax' sẽ khá đơn giản. Và, ngoài ra, nó thực sự tỏ ra thuận tiện để vượt qua mỗi con trỏ cột một lần trong hàm và được áp dụng hầu như "bởi" nhóm. –

Các vấn đề liên quan