2014-02-05 3 views
4

У меня есть большой набор данных и таблица поиска. Мне нужно вернуть для каждой строки в наборе данных наименьшее значение, присутствующее для строк в поиске, где выполняются условия.R Множественное условие join using data.table

Учитывая размер моего набора данных, я неохотно взламываю решение iffy вместе, перекрестно соединяясь, так как это создаст много миллионов записей. Я надеюсь, что кто-то может предложить решение, которое (в идеале) использует базу r или data.table, поскольку они уже используются эффективным образом.

Пример

A<-seq(1e4,9e4,1e4) 
B<-seq(0,1e4,1e3) 

dt1<-data.table(expand.grid(A,B),ID=1:nrow(expand.grid(A,B))) 
setnames(dt1, c("Var1","Var2"),c("A","B")) 

lookup<-data.table(minA=c(1e4,1e4,2e4,2e4,5e4), 
       maxA=c(2e4,3e4,7e4,6e4,9e4), 
       minB=rep(2e3,5), 
       Val=seq(.1,.5,.1)) 

# Sample Desired Value 
    A  B ID Val 
99: 90000 10000 99 0.5 

В SQL, я бы тогда написать что-то вдоль линий

SELECT ID, A, B, min(Val) as Val 
FROM dt1 
LEFT JOIN lookup on dt1.A>=lookup.minA 
       and dt1.A<=lookup.maxA 
       and dt1.B>=lookup.minB 
GROUP BY ID, A, B 

который объединил бы все соответствующие записи из lookup в dt1 и возвращают наименьшее Val.

Update

Мое решение до сих пор выглядит следующим образом:

CJ.table<-function(X,Y) setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL] 

dt1.lookup<- CJ.table(dt1,lookup)[A>=minA & A<=maxA & B>=minB, 
            list(Val=Val[which.min(Val)]), 
            by=list(ID,A,B)] 
dt1.lookup<-rbind.fill(dt1.lookup, dt1[!ID %in% dt1.lookup$ID]) 

Это извлекает все записи и позволяет вернуть дополнительные столбцы из таблицы перекодировки, если они нужны мне. Он также имеет преимущество в обеспечении выбора минимума Val.

+1

примечание: использование 'CJ' (реализовано в пакете' data.table') будет быстрее, чем 'expand.grid'. – Arun

+0

@ Арун - отличный совет! Большое спасибо –

+0

см. Мое редактирование для незначительной настройки вашего существующего решения. –

ответ

1

Решение я нашел без поперечного присоединения первых необходимо подготовить данные, избавившись от строк, где A и B находятся вне диапазона целиком:

Prep = dt1[A >= min(lookup$minA) & A <= max(lookup$maxA) & B >= min(lookup$minB)] 

Затем вы делаете таблицу данных о том, где каждый из условия, которые соответствуют минимально возможной Val:

Indices = Prep[,list(min(which(A >= lookup$minA)), 
        min(which(A <= lookup$maxA)), 
        min(which(B >= lookup$minB)), A, B),by=ID] 

Тогда вы должны получить Val в самой нижней точке, где все три условия:

Indices[,list(Val=lookup$Val[max(V1,V2,V3)], A, B),by=ID] 

Смотрите, если это получает вас, что вы ищете:

ID Val  A  B 
1: 19 0.1 10000 2000 
2: 20 0.1 20000 2000 
3: 21 0.2 30000 2000 
4: 22 0.3 40000 2000 
5: 23 0.3 50000 2000 
6: 24 0.3 60000 2000 
7: 25 0.3 70000 2000 
8: 26 0.5 80000 2000 
9: 27 0.5 90000 2000 
10: 28 0.1 10000 3000 
+0

Отличная идея! Обернет мне голову и вернусь к вам –

+0

Спасибо за это @Senor, к сожалению, это выглядит не так, чтобы гарантировать, что минимальный Val выбран, когда соответствует более одной записи поиска. Перекрестное соединение плохо для производительности в этом случае, но я принял ваше решение и несколько изменил его на основе другого ответа SO re: cross joins http://stackoverflow.com/questions/10600060/how-to-do-cross-join -in-r/14165493 # 14165493 –

1

Моя первая мысль пытается сделать индекс, как сеньор O сделал. Тем не менее, мин (Val) сделал индексный стол более жестким для меня, чтобы продумать. То, как я думал сделать это, - это прокрутить таблицу поиска.

dt1[,Val:=as.numeric(NA)] 
for (row in 1:NROW(lookup)) { 
    dt1[A>=lookup[order(Val)][row,minA]&A<=lookup[order(Val)][row,maxA]&B>=lookup[order(Val)][row,minB]&is.na(Val),Val:=lookup[order(Val)][row,Val]] 
    } 

Я думаю, что это должно работать, потому что сначала устанавливает новый столбец с NA значениями.

Затем он помещает таблицу поиска в порядке Val, поэтому вы получите самое низкое значение.

В каждом цикле он будет только потенциально изменяет значения в dt1, если они еще NA в Val и так как мы пробегаем по lookup в порядке Наименьшее Val к самым большим он будет гарантировать, что Вы получите min(Val), что вы хотели.

заменить rbind.fill линию

rbindlist(list(dt1.lookup,dt1[!ID %in% dt1.lookup[,ID]][,list(ID, A, B, Val=as.numeric(NA))])) 

это устранит зависимость от reshape пакета, и я думаю, что это будет быстрее.

+2

Я не уверен, что концепция цикла применима для моих записей 800k - обязательно посмотрит, хотя –

+1

Я сделал пару быстрых тестов 'system.time', а Senor's примерно в 3 раза быстрее, когда я увеличено 'dt1' до 900900 строк, но до 1 секунды для обоих. Я был на полпути, написав мой, когда ответ Сеньора появился, по крайней мере, на моем экране, поэтому я чувствовал себя готовым к финишу. –

+1

Спасибо, Дин, к сожалению, это оказалось слишком трудоемким, когда моя таблица поиска составляла более 300 записей, и в моей реальной задаче, где мне нужно основывать выбор на производной переменной от Val, это оказалось выше моего мастерства, чтобы заставить ее работать как требуется. Я опубликовал свое текущее рабочее решение, поэтому, если у вас есть какие-то другие идеи, я был бы благодарен за их прослушивание –

Смежные вопросы