Как сделать нечеткое соединение в R, используя более одной переменной с каждой стороны

Я хотел бы присоединиться к двум кадрам данных:

a <- data.frame(x=c(1,3,5))
b <- data.frame(start=c(0,4),end=c(2,6),y=c("a","b"))

с условием типа (x>start)&(x<end), чтобы получить такой результат:

#  x    y
#1 1    a
#2 2 <NA>
#3 3    b

Я не хочу делать потенциально большое декартово произведение, а затем выбирать только несколько строк, соответствующих условию, и мне нужно решение с использованием tidyverse (меня не интересует решение с использованием SQL, которое было бы признанием неудачи) . Я думал о пакете «fuzzyjoin», но не могу найти примеры, соответствующие моей потребности: функция, применяемая для условия, имеет только два аргумента. Я также попытался поместить «начало» и «конец» в один аргумент с помощью data.frame(z=I(purrr::map2(b$start,b$end,list)),y=b$y) # z y #1 0, 2 a #2 4, 6 b

но хотя данные выглядят нормально, fuzzy_left_join их не принимает.

Я ищу решения, работающие в более общих случаях (n переменных в левой части, m в правой, не обязательно числовых с произвольными условиями).

ОБНОВЛЕНИЕ

Я также хочу иметь возможность выражать условия, такие как (x=start+1)|(x=end+1) здесь:

#   x  y
#1  1  a
#2  3  a
#3  5  b

person Nicolas2    schedule 29.05.2018    source источник


Ответы (5)


В этом случае вам не нужны multi_by или multy_match_fun, это работает:

library(fuzzyjoin)
fuzzy_left_join(a, b, by = c(x = "start", x = "end"), match_fun = list(`>`, `<`))
#   x start end    y
# 1 1     0   2    a
# 2 3    NA  NA <NA>
# 3 5     4   6    b
person Moody_Mudskipper    schedule 02.03.2019

В конце концов я обратился к коду fuzzy_join и нашел способ сделать то, что хочу, даже без надлежащей документации. fuzzy_let_join не работает, но есть следующий способ (не очень красивый, и на самом деле это декартово произведение):

g <- function(x,y) (x>y[,"start"])&(x<y[,"end"])
fuzzy_join(a,b, multi_by = list(x="x",y=c("start","end"))
              , multi_match_fun = g, mode = "left") %>% select(x,y)
person Nicolas2    schedule 30.05.2018

data.table подход может быть

library(data.table)

name1 <- setdiff(names(setDT(b)), names(setDT(a))) 
#perform left outer join and then select required columns
a[b, (name1) := mget(name1), on = .(x > start, x < end)][, .(x, y)]

который дает

   x    y
1: 1    a
2: 3 <NA>
3: 5    b

Пример данных:

a <- data.frame(x = c(1, 3, 5))
b <- data.frame(start = c(0, 4), end = c(2, 6), y = c("a", "b"))



Обновление: если вы хотите соединить оба фрейма данных при условии (x=start+1)|(x=end+1), вы можете попробовать

library(data.table)

DT1 <- as.data.table(a)
DT2 <- as.data.table(b)

#Perform 1st join on "x = start+1" and then another on "x = end+1". Finally row-bind both results.
DT <- rbindlist(list(DT1[DT2[, start_temp := start+1], on = c(x = "start_temp"), .(x, y), nomatch = 0], 
                     DT1[DT2[, end_temp := end+1], on = c(x = "end_temp"), .(x, y), nomatch = 0]))
DT
#   x y
#1: 1 a
#2: 5 b
#3: 3 a
person 1.618    schedule 29.05.2018
comment
Это отлично работает в моем примере, но мне не удалось использовать свое условие в том виде, в котором я его дал (целиком), или использовать что-то вроде (x+y)›start. Какой будет синтаксис? - person Nicolas2; 30.05.2018
comment
on критерии, упомянутые в моем ответе, это (xstart) и (xend). Поэтому я действительно не уверен, понял ли я ваш дополнительный вопрос. Не могли бы вы поделиться своими новыми входными данными и желаемым результатом? - person 1.618; 30.05.2018
comment
› a[b, (name1) := mget(name1), on = .((x › start)&(x ‹ end))][, .(x, y)] Ошибка в [.data.table(a, b, := ((name1), mget(name1)), on = .((x › : Столбцы) [(x] не найдены в x - person Nicolas2; 30.05.2018
comment
Запустите код, указанный в моем ответе, как есть. on = .(x > start, x < end) означает (x › начало) и (x ‹ конец), поэтому вам не нужно явно упоминать, как вы это сделали в предыдущем комментарии. Если мой код выдает ошибку, поделитесь воспроизводимым примером вместе с журналом ошибок для того же. - person 1.618; 30.05.2018
comment
В моем первом вопросе нужно было иметь дело с более сложным примером, чем просто быть между двумя значениями и другими соединителями, чем «и» (даже если это кажется неполным). - person Nicolas2; 30.05.2018
comment
Да, это работает. Кто-то предложил мне что-то подобное для второго конкретного случая: просто выполните два подзапроса и объедините их. Но мой вопрос был явно недостаточно ясен. То, что я искал, было каким-то общим ответом, который мог бы позволить, например, указать фактическое состояние в виде простого параметра, например, в dplyr::filter. это скорее вопрос метапрограммирования, чем вопрос решения конкретного случая соединения двух конкретных наборов данных при определенных условиях. Должен ли я программировать решение самостоятельно? - person Nicolas2; 01.06.2018
comment
Я думаю, чтобы обобщить это, вы можете рассмотреть вариант написания функции. Одним из подходов может быть определение типа условия и, соответственно, передача потока вашей функции для выполнения типа «Join». Вы также можете опубликовать новый вопрос, перефразировав его соответствующим образом. - person 1.618; 01.06.2018

Возможный ответ, чтобы объяснить, что я пытаюсь сделать: каким-то образом расширить dplyr. И я буду рад узнать, есть ли способы улучшить это решение или какие-то проблемы, которых я не заметил. Решение избегает декартова произведения, но дублирует в списках фреймов данных как один из фреймов входных данных, так и результат. Я не включил окончательный выбор столбца x и y, который легко закодировать.

my_left_join <- function(.DATA1,.DATA2,.WHERE)
  {
  call = as.list(match.call())
  df1 <- .DATA1
  df1$._row_ <- 1:nrow(df1)
  dfl1 <- replyr::replyr_split(df1,"._row_")
  eval(substitute(
    dfl2 <- mapply(function(.x) 
                  {filter(.DATA2,with(.x,WHERE)) %>%
                   mutate(._row_=.x$._row_)}
                  , dfl1, SIMPLIFY=FALSE)
    ,list(WHERE=call$.WHERE))) 
  df2 <- replyr::replyr_bind_rows(dfl2)
  left_join(df1,df2,by="._row_") %>% select(-._row_)
  }

my_left_join(a,b,(x>start)&(x<end))
#  x start end    y
#1 1     0   2    a
#2 3    NA  NA <NA>
#3 5     4   6    b

my_left_join(a,b,(x==(start+1))|(x==(end+1)))
#  x start end y
#1 1     0   2 a
#2 3     0   2 a
#3 5     4   6 b
person Nicolas2    schedule 01.06.2018

Вы можете попробовать GenomicRanges решение

library(GenomicRanges)
# setup GRanges objects
a_gr <- GRanges(1, IRanges(a$x,a$x))
b_gr <- GRanges(1, IRanges(b$start, b$end))
# find overlaps between the two data sets
res <- as.data.frame(findOverlaps(a_gr,b_gr))
# create the expected output
a$y <- NA
a$y[res$queryHits] <- as.character(b$y)[res$subjectHits]
a
  x    y
1 1    a
2 3 <NA>
3 5    b
person Roman    schedule 29.05.2018