【问题标题】:Count rows matching a criteria relative to current row计数与当前行相关的条件匹配的行
【发布时间】:2019-03-05 16:41:41
【问题描述】:

我有一个这样结构的数据框(但它实际上有大约 40 万行):

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")

(已编辑,感谢 Uwe)

对于每一行,我想计算数据框中有多少行具有与当前行相同的 id 以及与当前行中的时间段重叠的起始时间段。例如,对于第一行,结果将为 2,因为还有另一行的 id = 174095,并且它的结尾大于第一行的开头。

我试着用 dplyr 的 rowwise 来做,比如:

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

@Uwe 的建议解决方案:

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

适用于上面的示例 data.frame。但事实证明,示例说明性不够,或者我并没有真正让自己理解:)

我为 id 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")

现在,id 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”完成,因此具有相同 id 的所有行都具有相同的结果。

我的解决方案还包括通过“开始”和“结束”执行最终合并:

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

这正是我所期望的!

【问题讨论】:

  • Frank 的方法df[, n := df[.SD, on = .(id, start &lt;= end, end &gt;= start), .N, by = .EACHI]$N][] 也为已编辑的数据集返回了正确答案。使用第二个连接时确实存在问题,可以通过包含行号来修复(请参阅我更新的答案)。
  • 非 equi 连接中结果列的命名方式已在 GitHub 上多次报告,github.com/Rdatatable/data.table/pull/3093 正在解决此问题。

标签: r dplyr data.table


【解决方案1】:

编辑 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 的部分是基于Frank's comment


如果同一 idpointed out by the OP 的计数不同,我最初尝试使用第二个连接将结果附加到原始 df 失败。这可以通过在第二个连接中包含行号来解决:

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

说明

非等连接中的连接条件可以解决问题。如果第一个间隔在第二个间隔开始之前结束或第一个间隔在第二个间隔结束后开始,则两个间隔重叠,

e12 或 e21

现在,如果两个区间 do 相交/重叠,那么与上述相反的情况必定成立。通过否定和应用德摩根定律,我们得到了条件

s21 AND e2 >= s1

非等连接中使用

数据

OP 的 EDIT 2019-03-06 中描述的 OP 扩展数据集:

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")

【讨论】:

  • @Frank 非常感谢您的改进。我不确定tmp 的正确行顺序,所以为了安全起见,我使用了另一个连接。另外,我不知道我可以在自我加入中使用.SD - 很棒的发现!。
  • 我只是想让您知道,我已经进一步测试了您的解决方案,但发现了一个问题。幸运的是,我已经能够自己修补它,但如果您对它的全部内容感兴趣,我已经在原始帖子的底部进行了解释。再次感谢!
【解决方案2】:

我最初误解了这个问题,我认为@Uwe 的方法是要走的路。在我的第一个答案中,我使用data.table 来识别每个id 的后续日期的组(以及一组中有多少行),显然不是您想要的。

这里还有一个简短的 sqldfsn-p 来补充 @Uwe 的方法(虽然不够充分,因为这里没有保留行顺序 - 这需要一些额外的修补):

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

【讨论】:

  • 不错的解决方案!不要忘记setDT(df)
  • 那句sqldf语句是我尝试过的想法之一。它为示例数据帧完成了工作,但对于真正的 400k 行数据帧,它需要不合理的时间才能完成。无论如何,非常感谢您的宝贵时间,@arg0naut!
  • 不客气!确实,data.table 是完全同意的方法!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-24
  • 2019-09-27
  • 1970-01-01
  • 1970-01-01
  • 2018-05-07
  • 1970-01-01
相关资源
最近更新 更多