2013-12-16 17 views
10

Chỉ cần tự hỏi nếu có một cách slicker để tập hợp một data.table. Về cơ bản tôi có một cái bàn lớn với hàng triệu và hàng trăm cols. Tôi muốn subset nó dựa trên một số nguyên col/s có một giá trị giữa một phạm vi được xác định bởi tôi.R: data.table subsetting dựa trên một cột số nguyên

Tôi đã tự hỏi liệu việc đặt cột có liên quan là Khóa sẽ là tìm kiếm nhị phân nhưng sau đó không chắc chắn liệu tôi có thể tìm thấy các hàng giữa một phạm vi giá trị hay không.

Ví dụ được giải thích bên dưới.

> n = 1e7 
> dt <- data.table(a=rnorm(n),b=sample(letters,replace=T,n)) 
> system.time(subset(dt, a > 1 & a < 2)) 
    user system elapsed 
    1.596 0.000 1.596 
> system.time(dt[a %between% c(1,2)]) 
    user system elapsed 
    1.168 0.000 1.168 

có thể làm như thế này không?

setkey(dt,a) 
dt[ ] : get me the rows between 1 and 2 values of the key 

Cảm ơn! -Abhi

+0

'between' sẽ không lưu bất kỳ lúc nào vì nó chứa mã' x> = lower & x <= upper'. 'dt [a> 1 & a <2]' sẽ chỉ nhanh như –

+0

cách sử dụng thiết lập khóa? Tôi vừa cập nhật câu hỏi của mình không chắc chắn rằng tôi có thể thực hiện tìm kiếm có phạm vi trên một khóa. – Abhi

Trả lời

1

Tôi không phải là một chuyên gia data.table, nhưng từ những gì tôi hiểu được lý do tìm kiếm keysetkey(dt, b) ; dt['a'] là quá nhanh là vì nó sử dụng tìm kiếm nhị phân thay vì vector quét. Điều đó là không thể đối với các cột số khi đặt số yêu cầu toán tử nhị phân.

Lựa chọn duy nhất sẽ làm điều gì đó như:

dt[,Between:=ifelse(a > 1 & a < 2, 'yes', 'no')] 
setkey(dt, Between) 
> system.time(dt['yes']) 
    user system elapsed 
    0.04 0.00 0.03 

nào, thú vị, nhanh hơn cả:

Index = dt[,a > 1 & a < 2] 
> system.time(dt[Index]) 
    user system elapsed 
    0.23 0.00 0.23 

Nhưng kể từ khi bạn chỉ có thể tiết kiệm một tập hợp con như một dữ liệu riêng biệt. bảng anyway, tôi không thấy điều này có nhiều ứng dụng.

7

Thực hiện setkey ở đây sẽ tốn kém (ngay cả khi bạn sử dụng đặt hàng nhanh trong 1.8.11), bởi vì nó cũng phải di chuyển dữ liệu (theo tham chiếu).

Tuy nhiên, bạn có thể giải quyết trường hợp này bằng cách sử dụng chức năng floor. Về cơ bản, nếu bạn muốn tất cả các số trong [1,2] (Lưu ý: bao gồm 1 và 2 ở đây), thì floor sẽ cung cấp giá trị "1" cho tất cả các giá trị này. Tức là, bạn có thể làm:

system.time(t1 <- dt[floor(a) == 1]) 
# user system elapsed 
# 0.234 0.001 0.238 

Điều này tương đương với làm dt[a >= 1 & a <=2] và nhanh gấp hai lần.

system.time(t2 <- dt[a >= 1 & a <= 2]) 
# user system elapsed 
# 0.518 0.081 0.601 

identical(t1,t2) # [1] TRUE 

Tuy nhiên, kể từ khi bạn không muốn sự bình đẳng, bạn có thể sử dụng một hack để trừ đi khoan dung = .Machine$double.eps^0.5 từ cột a. Nếu giá trị nằm trong phạm vi [1, 1+tolerance), thì nó vẫn được coi là 1. Và nếu nó chỉ là nhiều hơn, thì nó không còn 1 nữa (nội bộ). Tức là, đó là số nhỏ nhất> 1 mà máy có thể xác định là không 1. Vì vậy, nếu bạn trừ 'a' bằng dung sai tất cả các số được biểu diễn bên trong là "1" sẽ trở thành < 1 và floor(.) sẽ dẫn đến 0. Vì vậy, Thay vào đó, bạn sẽ nhận được dải ô> 1 và < 2. Tức là,

dt[floor(a-.Machine$double.eps^0.5)==1] 

sẽ cho kết quả tương đương là dt[a>1 & a<2].


Nếu bạn đã làm điều này nhiều lần, sau đó có thể tạo ra một cột mới với floor chức năng này và thiết lập quan trọng trên đó integer cột có thể giúp:

dt[, fa := as.integer(floor(a-.Machine$double.eps^0.5))] 
system.time(setkey(dt, fa)) # v1.8.11 
# user system elapsed 
# 0.852 0.158 1.043 

Bây giờ, bạn có thể truy vấn bất kỳ phạm vi bạn muốn sử dụng tìm kiếm nhị phân:

> system.time(dt[J(1L)]) # equivalent to > 1 & < 2 
# user system elapsed 
# 0.071 0.002 0.076 
> system.time(dt[J(1:4)]) # equivalent to > 1 & < 5 
# user system elapsed 
# 0.082 0.002 0.085 
8

Nếu bạn làm thiết lập các phím trên a (sẽ mất một thời gian (14,7 giây trên máy của tôi cho n=1e7), thì bạn có thể sử dụng các phép nối cán để xác định điểm bắt đầu và kết thúc của khu vực bạn quan tâm.

# thus the following will work. 
dt[seq.int(dt[.(1),.I,roll=-1]$.I, dt[.(2), .I, roll=1]$.I)] 


n = 1e7 
dt <- data.table(a=rnorm(n),b=sample(letters,replace=T,n)) 
system.time(setkey(dt,a)) 
# This does take some time 
# user system elapsed 
# 14.72 0.00 14.73 
library(microbenchmark) 
f1 <- function() t1 <- dt[floor(a) == 1] 
f2 <- function() t2 <- dt[a >= 1 & a <= 2] 
f3 <- function() {t3 <- dt[seq.int(dt[.(1),.I,roll=-1]$.I, dt[.(2), .I, roll=1]$.I)] } 
microbenchmark(f1(),f2(),f3(), times=10) 
# Unit: milliseconds 
# expr  min  lq median  uq  max neval 
# f1() 371.62161 387.81815 394.92153 403.52299 489.61508 10 
# f2() 529.62952 536.23727 544.74470 631.55594 634.92275 10 
# f3() 65.58094 66.34703 67.04747 75.89296 89.10182 10 

Bây giờ là "nhanh", nhưng vì chúng tôi đã dành thời gian để thiết lập khóa.

Thêm phương pháp @ Eddi cho benchmarking

f4 <- function(tolerance = 1e-7){ # adjust according to your needs 
    start = dt[J(1 + tolerance), .I[1], roll = -Inf]$V1 
    end = dt[J(2 - tolerance), .I[.N], roll = Inf]$V1 
if (start <= end) dt[start:end]} 
microbenchmark(f1(),f2(),f3(),f4(), times=10) 
# Unit: milliseconds 
# expr  min  lq median  uq  max neval 
# f1() 373.3313 391.07479 440.07025 488.54020 491.48141 10 
# f2() 523.2319 530.11218 533.57844 536.67767 629.53779 10 
# f3() 65.6238 65.71617 66.09967 66.56768 83.27646 10 
# f4() 65.8511 66.26432 66.62096 83.86476 87.01092 10 

cách tiếp cận Eddi là một chút an toàn hơn vì nó sẽ chăm sóc của khoan dung dấu chấm động.

5

Nếu bạn có một bộ chìa khóa, sau đó dữ liệu của bạn được sắp xếp, vì vậy chỉ cần tìm ra các điểm cuối và lấy điểm ở giữa:

setkey(dt, a) 
tolerance = 1e-7 # adjust according to your needs 
start = dt[J(1 + tolerance), .I[1], roll = -Inf]$V1 
end = dt[J(2 - tolerance), .I[.N], roll = Inf]$V1 
if (start <= end) dt[start:end] 

Đây sẽ là một chút chậm hơn so với phương pháp floor Arun, vì nó 2 tham gia, nhưng ở bên cộng, bạn có thể cắm bất kỳ số nào bạn thích.

+0

cho rằng các kết nối cán sẽ chỉ tạo ra một giá trị trả về duy nhất, bạn có thể tránh được '[1]' '[.N]' subsetting. – mnel

+1

Tôi chắc chắn 'roll' sẽ sớm ra mắt sau này :) – Arun

+1

@mnel thực sự không phải như vậy, hãy thử' data.table (a = c (1,1,1), key = 'a') [J (1), roll = Inf] ' – eddi

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