#r #date-range
#r #диапазон дат
Вопрос:
Я работаю с данными о фертильности крупного рогатого скота. В одной таблице (фрейме данных) у меня есть запись всех услуг, выполненных в корове (например, осеменения). В другой таблице я получаю диагноз беременности (положительный или отрицательный). Оба имеют уникальный идентификатор (animal_id). Моей задачей было успешное объединение обеих таблиц в правильном диапазоне данных, что означает, что мне нужна проверка на беременность, связанная с правильной записью об осеменении. Вот пример того, как выглядят обе таблицы,
animal_id service_date
610710 2005-10-22
610710 2006-12-03
610710 2006-12-27
610710 2007-12-02
610710 2008-01-17
610710 2008-03-04
Другая таблица такая же, но с другой датой (event_date) и диагнозом,
animal_id event_date event_description
610710 2006-06-16 PP
610710 2007-02-15 PP
610710 2008-01-09 PN
610710 2008-04-09 PP
610710 2009-06-16 PP
Итак, что я хотел бы сделать, так это объединить обе таблицы таким образом, чтобы даты дополняли друг друга, то есть, если служба была выполнена в 2005-10-12, когда я соединю обе таблицы, эта строка будет ссылаться на ближайшую дату в таблице событий, и под ближайшей я также подразумеваю позже — поскольку оплодотворение происходит до постановки диагноза. Таким образом, желаемый результат будет примерно таким,
animal_id service_date event_date event_description
1 610710 2005-10-22 NA NA
2 610710 NA 2006-06-16 PP
3 610710 2006-12-03 2007-02-15 PP
4 610710 2006-12-27 2007-02-15 PP
5 610710 2007-12-02 2008-01-09 PN
6 610710 2008-01-17 2008-04-09 PP
7 610710 2008-03-04 NA NA
8 610710 NA 2009-06-16 PP
В конечном результате я ожидаю, что большое количество записей не будет объединяться с чем-либо, например, с строкой 1 в примере вывода. В октябре 2005 года была проведена служба, но первый диагноз, который я поставил для этой коровы, был поставлен в июне 2006 года — вероятно, отсутствует ряд записей об обслуживании. К сожалению, этого следовало ожидать. Для этого примера имеют смысл только строки 5 и 6. Для строк 3 и 4 я бы рассмотрел только строку 4, поскольку это, вероятно, оплодотворение, которое привело к беременности.
Возможно ли это вообще в R?
Спасибо!
Комментарии:
1. Можете ли вы также обмениваться данными из events dataframe? Также, каков диапазон дат, в пределах которого должно произойти слияние?
2. Привет @KarthikS, спасибо за ваш комментарий. Я также добавил данные из событий. Сложность диапазона дат заключается в том, что он «гибкий» (если не сказать больше)….. Все, что происходит до 60 дней после service_date, было бы значительным
3. Для ясности было бы полезно, если бы вы предоставили ожидаемый результат для этого образца данных. Спасибо! (Я подозреваю, что лучше всего это будет сделано с помощью одного из:
data.table
, SQL/sqldf
, илиfuzzyjoin
.)4. Привет @r2evans, спасибо за ваш комментарий. Я добавил пример выходных данных и некоторую дополнительную информацию, надеюсь, это будет полезно. Спасибо!
5. Почему fhe 2006-06-16 event_date не сопоставляется с service_date 2005-10-22?
Ответ №1:
То, что вы запрашиваете, — это соединение «неравномерное» или «диапазон». Это не поддерживается базовым R (или dplyr
отсутствует dbplyr
), но может быть выполнено с некоторыми другими пакетами.
Для всех я создаю event_date_lag
так, чтобы мы ограничивали количество возвратов для каждой строки. (Без этого мы получили бы несколько совпадений.)
fuzzyjoin
out <- fuzzyjoin::fuzzy_full_join(
services, events,
by = c("animal_id" = "animal_id",
"service_date" = "event_date_lag",
"service_date" = "event_date"),
match_fun = list(`==`, `>=`, `<=`))
# not sure why fuzzyjoin is splitting animal_id
out <- transform(out, animal_id = ifelse(is.na(animal_id.x), animal_id.y, animal_id.x))
out$animal_id.x <- out$animal_id.y <- out$event_date_lag <- NULL
# ordering here primarily to compare with your desired output
out[with(out, order(ifelse(is.na(service_date), event_date, service_date))),]
# service_date event_date event_description animal_id
# 6 2005-10-22 <NA> <NA> 610710
# 7 <NA> 2006-06-16 PP 610710
# 1 2006-12-03 2007-02-15 PP 610710
# 2 2006-12-27 2007-02-15 PP 610710
# 3 2007-12-02 2008-01-09 PN 610710
# 4 2008-01-17 2008-04-09 PP 610710
# 5 2008-03-04 2008-04-09 PP 610710
# 8 <NA> 2009-06-16 PP 610710
sqldf
SQL в целом поддерживает концепцию неравнозначных или диапазонных объединений. В пакете нет ничего особенного sqldf
, просто он обеспечивает собственный интерфейс SQL (via RSQLite
) без накладных расходов или хлопот, связанных с загрузкой ваших данных в СУБД SQL и извлечением их обратно в этом запросе. Хотя это на самом деле то, что происходит с sqldf
, это автоматизирует большую часть этого, позволяя работать непосредственно с объектами R с использованием SQL.
Если случайно вы уже получаете свои данные из СУБД, то соединение SQL, безусловно, является наиболее эффективным: соедините их в источнике.
sqldf::sqldf(
"select svc.animal_id, svc.service_date,
ev.event_date, ev.event_description
from services svc
left join events ev on svc.animal_id=ev.animal_id
and svc.service_date between ev.event_date_lag and ev.event_date
order by svc.service_date, ev.event_date")
# animal_id service_date event_date event_description
# 1 610710 2005-10-22 <NA> <NA>
# 2 610710 2006-12-03 2007-02-15 PP
# 3 610710 2006-12-27 2007-02-15 PP
# 4 610710 2007-12-02 2008-01-09 PN
# 5 610710 2008-01-17 2008-04-09 PP
# 6 610710 2008-03-04 2008-04-09 PP
данные.таблица
Хотя я часто использую это, если вы еще не используете его, то это может быть немного больше, чем вам нужно (его кривая обучения, хотя и стоит того, может быть крутой).
Примечания:
-
семантика
data.table
-(Y[X]
, которая фактически является «X left join Y») поддерживает внутренние, левые и правые, но не полные, полу- или антисоединения. Хотя это может быть возможно с использованием перекрестного соединения (декартова произведения), это приводит к резкому увеличению использования памяти и является (imo) не лучшим способом. -
объединение имеет тенденцию переименовывать переменные левой стороны (
X
inY[X]
) в переменные справа. Это может сбить с толку, и фактически это может замаскировать фактические значения перед слиянием, поэтому я продублируюservice_date
, чтобы сохранить их отдельно. -
Я использую
as.data.table
здесь только для ответа SO, а не потому, что требуется различатьdata.frame
data.table
переменные и . Если вы переключаетесь наdata.table
, тоsetDT
это канонический путь. -
Если вы выберете это, но не продолжите другие
data.table
операции, убедитесь, что вы преобразовали обратно в обычныйdata.frame
режим, используяsetDF
илиas.data.frame
; есть достаточно тонкие различия, которые не делают этого, будет проблемой.
library(data.table)
svcDT <- as.data.table(services)
evDT <- as.data.table(events)
evDT[svcDT[,sdate:=service_date],
on = .(animal_id == animal_id, event_date_lag <= sdate, event_date >= sdate)
][, event_date_lag := NULL ]
# animal_id event_date event_description service_date
# 1: 610710 2005-10-22 <NA> 2005-10-22
# 2: 610710 2006-12-03 PP 2006-12-03
# 3: 610710 2006-12-27 PP 2006-12-27
# 4: 610710 2007-12-02 PN 2007-12-02
# 5: 610710 2008-01-17 PP 2008-01-17
# 6: 610710 2008-03-04 PP 2008-03-04
Данные
services <- read.table(header = TRUE, text = "
animal_id service_date
610710 2005-10-22
610710 2006-12-03
610710 2006-12-27
610710 2007-12-02
610710 2008-01-17
610710 2008-03-04")
services$service_date <- as.Date(services$service_date)
events <- read.table(header = TRUE, text = "
animal_id event_date event_description
610710 2006-06-16 PP
610710 2007-02-15 PP
610710 2008-01-09 PN
610710 2008-04-09 PP
610710 2009-06-16 PP")
events$event_date <- as.Date(events$event_date)
events$event_date_lag <- ave(events$event_date, events$animal_id, FUN=function(a) c(a[1][NA], a[-length(a)]))
events
# animal_id event_date event_description event_date_lag
# 1 610710 2006-06-16 PP <NA>
# 2 610710 2007-02-15 PP 2006-06-16
# 3 610710 2008-01-09 PN 2007-02-15
# 4 610710 2008-04-09 PP 2008-01-09
# 5 610710 2009-06-16 PP 2008-04-09
Комментарии:
1. Большое вам спасибо за варианты и за вашу помощь. К сожалению, ни один из них не сработал. Проблемы, скорее всего, с моей стороны (извините за это, я не опытный пользователь R). Опция fuzzyjoin завершается сбоем с ошибкой «Ошибка: векторная память исчерпана (достигнут предел?) — наборы данных очень большие. Параметры sqldf не работают, потому что я не могу правильно загрузить пакет (проблема с xcrun, также пытаюсь решить эту проблему). data.table ни во что не выводится. Он запускается, не жалуется, но не генерирует выходные данные. Я обновлю вас, как только устраню проблемы. Спасибо за вашу помощь!
Ответ №2:
Используя входные данные, воспроизводимые в примечании в конце, свяжите их вместе, используя rbind_rows
, а затем отсортируйте их по дате, используя arrange
. Затем определите логический столбец collapse
, который является ИСТИННЫМ, если текущая строка имеет a service_date
, а следующая строка имеет an event_date
, и они разделены меньше или равны 90 дням — измените 90 на то, что вы хотите. Затем группируйте по animal_id
и номер группы, который увеличивается на 1 каждый раз service_date
, когда встречается a, и далее группируйте по строкам, за исключением случаев, когда текущая строка имеет collapse
значение TRUE, затем поместите ее в ту же группу, что и следующая строка, чтобы она соответствовала этой следующей строке event_date
. Наконец, суммируйте группы и удалите временные столбцы.
Обратите внимание, что этот подход поддерживает строки событий, которые не имеют соответствующих дат обслуживания, а также гарантирует, что каждая дата события не сопоставляется более чем с одной датой обслуживания.
library(dplyr)
bind_rows(DF1, DF2) %>%
arrange(coalesce(service_date, event_date)) %>%
group_by(animal_id, group = cumsum(!is.na(service_date))) %>%
mutate(collapse = !is.na(service_date) amp; !is.na(lead(event_date)) amp;
lead(event_date) - service_date <= 90) %>%
group_by(n = 1:n() collapse, .add = TRUE) %>%
summarize(animal_id = first(animal_id),
service_date = first(service_date),
event_date = last(event_date),
event_description = last(event_description), .groups = "drop") %>%
select(-group, -n)
предоставление:
# A tibble: 8 x 4
animal_id service_date event_date event_description
<int> <date> <date> <chr>
1 610710 2005-10-22 NA <NA>
2 610710 NA 2006-06-16 PP
3 610710 2006-12-03 NA <NA>
4 610710 2006-12-27 2007-02-15 PP
5 610710 2007-12-02 2008-01-09 PN
6 610710 2008-01-17 NA <NA>
7 610710 2008-03-04 2008-04-09 PP
8 610710 NA 2009-06-16 PP
sqldf
Мы можем следовать практически той же логике, используя пакет sqldf:
library(sqldf)
sqldf("with b0 as
(select *, NULL event_date, NULL event_description from DF1
union
select animal_id, NULL service_date, event_date, event_description from DF2),
b1 as (select *, coalesce(service_date, event_date) date1
from both order by animal_id, date1),
b2 as (select *, lead(event_date) over () lead_event_date
from b1),
b3 as (select *, coalesce(lead_event_date - service_date <= 90, 0)
row_number() over () coll
from b2)
select distinct animal_id,
group_concat(service_date) service_date,
group_concat(event_date) event_date,
group_concat(event_description) event_description
from b3 group by coll")
предоставление:
animal_id service_date event_date event_description
1 610710 2005-10-22 <NA> <NA>
2 610710 <NA> 2006-06-16 PP
3 610710 2006-12-03 <NA> <NA>
4 610710 2006-12-27 2007-02-15 PP
5 610710 2007-12-02 2008-01-09 PN
6 610710 2008-01-17 <NA> <NA>
7 610710 2008-03-04 2008-04-09 PP
8 610710 <NA> 2009-06-16 PP
Примечание
DF1 <- structure(list(animal_id = c(610710L, 610710L, 610710L, 610710L,
610710L, 610710L), service_date = structure(c(13078, 13485, 13509,
13849, 13895, 13942), class = "Date")), row.names = c(NA, -6L
), class = "data.frame")
DF2 <- structure(list(animal_id = c(610710L, 610710L, 610710L, 610710L,
610710L), event_date = structure(c(13315, 13559, 13887, 13978,
14411), class = "Date"), event_description = c("PP", "PP", "PN",
"PP", "PP")), row.names = c(NA, -5L), class = "data.frame")
Комментарии:
1. Большое вам спасибо за вашу помощь и комментарии! Я не смог попробовать опцию sqldf, потому что у меня возникли проблемы с загрузкой этого пакета (с помощью xcrun, пытаясь решить эту проблему), но первый вариант не дает мне того же результата, что и у вас.. Не уверен, почему. Я не получаю совпадающих дат, вместо этого я получаю одну строку для service_date и одну строку для events_date. Я изучаю это и обновлю вас, как только выясню, что не так, просто хотел поблагодарить вас за вашу помощь!
2. Скопируйте входные данные из примечания в конце, вставьте их в R, а затем запустите код в теле. Имейте в виду, что даты относятся к классу Date в Примечании. Мы не можем сказать, что у вас есть, поскольку вопрос не предоставил входные данные в воспроизводимой форме. Если вы не можете установить sqldf, то, вероятно, что-то не так с вашей установкой R. Если вы используете нестандартную сборку без tcltk, это может быть проблемой.