Как я могу объединить два набора данных с помощью пользовательской функции, которая применяет правило к нестандартным столбцам?

#r #function #date #merge

Вопрос:

Я пытаюсь объединить два фрейма данных разных размеров, но сталкиваюсь с трудностями из-за панельной структуры данных.

Рассмотрим пример ниже, где «toy.left» представляет собой панель из трех переменных: координаты («координаты») и имя («имя»), присвоенное этой координате в определенном месяце («месяц»). Затем рассмотрим «toy.right», который состоит из четырех переменных: имя («имя»), начало срока действия этого имени для назначения («срок действия.начало») для этой координаты и конец срока их пребывания для назначения («срок действия.конец») для этой координаты.

 toy.left <- tribble(~month, ~coord, ~name, 
"2000-01-01", 1301, "Alpha", 
"2000-03-01", 1301, "Beta", 
"2000-06-01", 1302, "Charlie", 
"2000-09-01", 1303, "Delta", 
"2000-12-01", 1303, "Epsilon")

toy.right <- tribble(~name, ~coord, ~tenure.start, ~tenure.end, 
"Alpha", 1301, "2000-02-01", "2000-04-01", 
"Beta", 1301, "1999-11-01", "2000-04-01", 
"Charlie", 1302,  "2000-04-01", "2000-07-01", 
"Delta", 1303, "2000-08-01", "2000-10-01", 
"Epsilon", 1303, "2000-11-01", "2001-01-01", 
"Delta", 1303, "2002-01-01", "2004-01-01")
 

Я хотел бы объединить эти два набора данных, но есть правила, которые затрудняют использование функции merge() в dplyr. Например, я не могу просто использовать inner_join() и объединять по «имени» и «координации», потому что это нарушает структуру панели данных. Если я это сделаю, срок пребывания человека не будет совпадать с месяцем наблюдения (во-первых, см. Строки 1 и 2, которые должны быть перевернуты; во-вторых, см. Строки 4 и 5, где merge() дублирует наблюдение за месяцем, но должен включать только строку 4).

 toy.left %>% 
  inner_join(toy.right, by = c("name", "coord"))

*Output*

month       coord   name   tenure.start tenure.end 
2000-01-01  1301    Alpha   2000-02-01  2000-04-01
2000-03-01  1301    Beta    1999-11-01  2000-04-01
2000-06-01  1302    Charlie 2000-04-01  2000-07-01
2000-09-01  1303    Delta   2000-08-01  2000-10-01
2000-09-01  1303    Delta   2002-01-01  2004-01-01
2000-12-01  1303    Epsilon 2000-11-01  2001-01-01

 

Чтобы решить эту проблему, я мог бы объединить данные по ‘имя’ ‘коорд,’ и ‘месяц’, но мне нужно условие сшивки «месяц» о том, является ли эта дата находится между ‘землевладения.запуск и владения.конец.’ После поиска вокруг, я не мог найти способ, чтобы применить настраиваемое правило для слияния() в dplyr.

Я понимаю, что пользовательская функция или цикл могут быть лучшим способом подойти к этому, но я не уверен, с чего начать. Кроме того, исходный набор данных содержит более 1,5 миллиона наблюдений, что может вызвать дополнительные проблемы.

Я приветствую ваши предложения!

Ответ №1:

(Все это после преобразования month и tenure.* в Date -класс.)

пушистое соединение

 fuzzyjoin::fuzzy_inner_join(
  toy.left, toy.right,
  by=c("name", "coord", month="tenure.start", month="tenure.end"), 
  match_fun=list(`==`, `==`, `>=`, `<=`))
# # A tibble: 4 x 7
#   month      coord.x name.x  name.y  coord.y tenure.start tenure.end
#   <date>       <dbl> <chr>   <chr>     <dbl> <date>       <date>    
# 1 2000-03-01    1301 Beta    Beta       1301 1999-11-01   2000-04-01
# 2 2000-06-01    1302 Charlie Charlie    1302 2000-04-01   2000-07-01
# 3 2000-09-01    1303 Delta   Delta      1303 2000-08-01   2000-10-01
# 4 2000-12-01    1303 Epsilon Epsilon    1303 2000-11-01   2001-01-01
 

sqldf

 sqldf::sqldf(
  "select tl.name, tl.coord, tl.month, tr.[tenure.start], tr.[tenure.end]
   from [toy.left] tl
     inner join [toy.right] tr on tl.name=tr.name and tl.coord=tr.coord
       and tl.month between tr.[tenure.start] and tr.[tenure.end]")
#      name coord      month tenure.start tenure.end
# 1    Beta  1301 2000-03-01   1999-11-01 2000-04-01
# 2 Charlie  1302 2000-06-01   2000-04-01 2000-07-01
# 3   Delta  1303 2000-09-01   2000-08-01 2000-10-01
# 4 Epsilon  1303 2000-12-01   2000-11-01 2001-01-01
 

(Я использую [tenure.start] обозначение в скобках , чтобы различать идентификатор таблицы tl и имя столбца tenure.start , где в SQL точки в именах столбцов обычно указывают schema.tablename.columnname -как номенклатура.)

данные.таблица

Это делает левые соединения, а не другие типы. Чтобы определить, какие из них следует удалить из-за того, что они сделаны слева, а не внутри, я добавлю столбец в toy.left :

 library(data.table)
setDT(toy.left)
setDT(toy.right)
toy.left[, val := 2]
toy.left[toy.right, on = .(name, coord, month >= tenure.start, month <= tenure.end)][ !is.na(val),]
#         month coord    name   val    month.1
#        <Date> <num>  <char> <num>     <Date>
# 1: 1999-11-01  1301    Beta     2 2000-04-01
# 2: 2000-04-01  1302 Charlie     2 2000-07-01
# 3: 2000-08-01  1303   Delta     2 2000-10-01
# 4: 2000-11-01  1303 Epsilon     2 2001-01-01
 

data.table имеет свой способ переименования столбцов, так что имейте это в виду. Когда я не уверен, что знаю, как в конечном итоге будет называться, я часто копирую столбцы вокруг, чтобы всегда было ясно … но отчасти причина, по которой я это делаю, заключается в лени в изучении того, как именно он определяет результирующие имена.