Подсчет строк, соответствующих критериям относительно текущей строки - PullRequest
4 голосов
/ 05 марта 2019

У меня есть структурированный фрейм данных (но на самом деле он содержит ~ 400 тыс. Строк):

library(data.table)
df <- fread("    id     start     end
174095 2018-12-19 2018-12-31
227156 2018-12-19 2018-12-31
210610 2018-04-13 2018-09-27
 27677 2018-04-12 2018-04-26
370474 2017-07-13 2017-08-19
303693 2017-02-20 2017-04-09
 74744 2016-10-03 2016-11-05
174095 2018-12-01 2018-12-20
 27677 2018-03-01 2018-05-29
111111 2018-01-01 2018-01-31
111111 2018-11-11 2018-12-31")

(отредактировано, спасибо Уве)

Для каждой строки я хочу подсчитать, сколько строк в кадре данных имеют тот же идентификатор, что и текущая строка, и начальный-конечный период, который перекрывает период в текущей строке. Например, для первой строки результат будет равен 2, поскольку есть другая строка с id = 174095, и ее конец больше, чем начало первой строки.

Я пытался сделать это с помощью dplyr, например:

df = df %>% rowwise() %>% mutate(count = sum(id == df$id & ((start >= df$start & start <= df$end) | (end >= df$start & end <= df$end))))

Но это очень медленно. Я попробовал, и через два часа он все еще работал.

Я также пытался использовать mapply, но это также отнимает слишком много времени:

df$count = mapply(function(id, start, end) {
return(sum(df$id == id & (between(df$start, start, end) | between(df$end, start, end))) }, id, start, end)

Есть ли эффективный разумный способ сделать это?

Большое спасибо


РЕДАКТИРОВАТЬ 2019-03-06

@ Предлагаемое Уве решение:

df[, overlapping.rows := df[.SD, on = .(id, start <= end, end >= start), .N, by = .EACHI]$N][]

отлично работает для примера data.frame выше. Но оказалось, что пример не был достаточно иллюстративным, или я не совсем понял, может быть:)

Я добавил третью запись для идентификатора 174095 и изменил две другие:

df <- fread("id     start     end
174095 2018-12-19 2018-12-31
            227156 2018-12-19 2018-12-31
            210610 2018-04-13 2018-09-27
            27677 2018-04-12 2018-04-26
            370474 2017-07-13 2017-08-19
            303693 2017-02-20 2017-04-09
            74744 2016-10-03 2016-11-05
            174095 2018-12-01 2018-12-18
            27677 2018-03-01 2018-05-29
            111111 2018-01-01 2018-01-31
            111111 2018-11-11 2018-12-31
            174095 2018-11-30 2018-12-25")

Теперь у идентификатора 174095 есть два интервала, которые не перекрываются между ними (строки 1 и 2), и другой интервал, который перекрывает два других (строка 3):

           id      start        end
1: 174095 2018-12-19 2018-12-31
2: 174095 2018-12-01 2018-12-18
3: 174095 2018-11-30 2018-12-25

Итак, результат должен быть:

       id      start        end overlapping.rows
1: 174095 2018-12-19 2018-12-31                2
2: 174095 2018-12-01 2018-12-18                2
3: 174095 2018-11-30 2018-12-25                3

Но на самом деле это так:

       id      start        end overlapping.rows
1: 174095 2018-12-19 2018-12-31                3
2: 174095 2018-12-01 2018-12-18                3
3: 174095 2018-11-30 2018-12-25                3

Если я не ошибаюсь, это происходит потому, что окончательное соединение выполняется только по "id", поэтому все строки с одинаковым идентификатором имеют одинаковый результат.

Мое решение состоит в выполнении окончательного слияния также с помощью «start» и «end»:

df[tmp, on = .(id, start, end), overlapping.rows := N]

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

setnames(tmp, c("id", "end", "start", "N"))

Теперь результат:

            id      start        end overlapping.rows
 1: 174095 2018-12-19 2018-12-31                2
 2: 227156 2018-12-19 2018-12-31                1
 3: 210610 2018-04-13 2018-09-27                1
 4:  27677 2018-04-12 2018-04-26                2
 5: 370474 2017-07-13 2017-08-19                1
 6: 303693 2017-02-20 2017-04-09                1
 7:  74744 2016-10-03 2016-11-05                1
 8: 174095 2018-12-01 2018-12-18                2
 9:  27677 2018-03-01 2018-05-29                2
10: 111111 2018-01-01 2018-01-31                1
11: 111111 2018-11-11 2018-12-31                1
12: 174095 2018-11-30 2018-12-25                3

Это именно то, что я ожидал!

Ответы [ 2 ]

6 голосов
/ 05 марта 2019

Изменить 2019-03-07, чтобы справиться с расширенным набором данных OP

Это можно решить с помощью агрегирования в неэквивалентном самосоединении

library(data.table)
# coerce character dates to IDate class
cols <- c("start", "end")
setDT(df)[, (cols) := lapply(.SD, as.IDate), .SDcols = cols]
# non-equi self-join and aggregate
tmp <- df[df, on = .(id, start <= end, end >= start), .N, by = .EACHI]
# append counts to original dataset
df[, overlapping.rows := tmp$N]
df
        id      start        end overlapping.rows
 1: 174095 2018-12-19 2018-12-31                2
 2: 227156 2018-12-19 2018-12-31                1
 3: 210610 2018-04-13 2018-09-27                1
 4:  27677 2018-04-12 2018-04-26                2
 5: 370474 2017-07-13 2017-08-19                1
 6: 303693 2017-02-20 2017-04-09                1
 7:  74744 2016-10-03 2016-11-05                1
 8: 174095 2018-12-01 2018-12-18                2
 9:  27677 2018-03-01 2018-05-29                2
10: 111111 2018-01-01 2018-01-31                1
11: 111111 2018-11-11 2018-12-31                1
12: 174095 2018-11-30 2018-12-25                3

Использование , связывающих код, можно записать более компактным, но и более запутанным способом:

library(data.table)
cols <- c("start", "end")
setDT(df)[, (cols) := lapply(.SD, as.IDate), .SDcols = cols][
  , overlapping.rows := df[df, on = .(id, start <= end, end >= start), .N, by = .EACHI]$N][]

Обратите внимание, что часть для добавления результатов к оригиналу df основана на комментарии Фрэнка .


Моя первоначальная попытка использовать второе соединение для добавления результатов к оригиналуdf не удалось в случае, если для того же id и , указанных в OP , имеются разные значения.Это можно исправить, включив номер строки во второе объединение:

library(data.table)
# coerce character dates to IDate class
cols <- c("start", "end")
setDT(df)[, (cols) := lapply(.SD, as.IDate), .SDcols = cols]
# append row number
tmp <- df[, rn := .I][
  # non-equi self-join and aggregate
  df, on = .(id, start <= end, end >= start), .(rn = i.rn, .N), by = .EACHI]
# append counts to original dataset by joining on row number
df[tmp, on = "rn", overlapping.rows := N][, rn := NULL]
df
        id      start        end overlapping.rows
 1: 174095 2018-12-19 2018-12-31                2
 2: 227156 2018-12-19 2018-12-31                1
 3: 210610 2018-04-13 2018-09-27                1
 4:  27677 2018-04-12 2018-04-26                2
 5: 370474 2017-07-13 2017-08-19                1
 6: 303693 2017-02-20 2017-04-09                1
 7:  74744 2016-10-03 2016-11-05                1
 8: 174095 2018-12-01 2018-12-18                2
 9:  27677 2018-03-01 2018-05-29                2
10: 111111 2018-01-01 2018-01-31                1
11: 111111 2018-11-11 2018-12-31                1
12: 174095 2018-11-30 2018-12-25                3

Объяснение

Условие соединения в соединении без равенства выполняеттрюк.Два интервала не перекрываются, если первый заканчивается до начала второго или первый интервал начинается после окончания второго интервала,

e 1 2 ИЛИ e 2 1

Теперь, если два интервала do пересекаются / перекрываются, тогда противоположностьвыше должно быть правдой.Отрицая и применяя закон Де Моргана , мы получаем условия

s 2 <= e <sub>1 И e 2 > = s 1

, которые используются в non-equi join .

Data

расширенный набор данных OP, как описанов ОП РЕДАКТИРОВАТЬ 2019-03-06:

library(data.table)
df <- fread("id     start     end
174095 2018-12-19 2018-12-31
227156 2018-12-19 2018-12-31
210610 2018-04-13 2018-09-27
27677  2018-04-12 2018-04-26
370474 2017-07-13 2017-08-19
303693 2017-02-20 2017-04-09
74744  2016-10-03 2016-11-05
174095 2018-12-01 2018-12-18
27677  2018-03-01 2018-05-29
111111 2018-01-01 2018-01-31
111111 2018-11-11 2018-12-31
174095 2018-11-30 2018-12-25")
3 голосов
/ 05 марта 2019

Сначала я неправильно понял вопрос, и я думаю, что подход @ Уве - это путь. В своем первом ответе я использовал data.table, чтобы определить группы (и сколько строк в группе) последовательных дат на id, очевидно, не то, что вы ищете.

Вот также короткий sqldf фрагмент, дополняющий подход @ Уве (хотя и не такой адекватный, так как здесь порядок строк не сохраняется - это потребует некоторых дополнительных действий):

library(sqldf)

df <- sqldf('SELECT id, start, end, COUNT(*) as overlappingRows FROM (SELECT df.* FROM df 
            LEFT OUTER JOIN df AS df2 
            ON df.id = df2.id AND df.start <= df2.end AND df.end >= df2.start) as origdf 
            GROUP BY id, start, end')

Выход:

       id      start        end overlappingRows
1   27677 2018-03-01 2018-05-29               2
2   27677 2018-04-12 2018-04-26               2
3   74744 2016-10-03 2016-11-05               1
4  111111 2018-01-01 2018-01-31               1
5  111111 2018-11-11 2018-12-31               1
6  174095 2018-12-01 2018-12-20               2
7  174095 2018-12-19 2018-12-31               2
8  210610 2018-04-13 2018-09-27               1
9  227156 2018-12-19 2018-12-31               1
10 303693 2017-02-20 2017-04-09               1
11 370474 2017-07-13 2017-08-19               1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...