合并具有重叠值的行

Merging rows with overlapping values

我有一个 data.table,其中每一行都有一个 UniqueID,一个年龄列,以及 2 个带有年龄置信区间的列。我想要做的是合并具有重叠 CI 的行,因此返回的 CI 是重叠的 min/max。

   ID   Age AgeMax AgeMin
1:  2 48073  49213  46933
2:  3 49002  49638  48366
3:  7 44297  44706  43888

此示例的返回结果将是:

ID  Age    AgeMax   AgeMin
2   48409   49638   46933
7   44297   44706   43888

因为 ID 2 和 3 在 AgeMax 和 AgeMin 中有重叠的值。 ID == 2 的 AgeMax 小于 ID == 3 的 AgeMax,但大于 ID == 3 的 AgeMin,因此它们重叠。 ID 7 不与其他行重叠,因此按原样返回。

实际上我不介意返回的 IDAge 是什么,只要它来自重叠的 ID 之一

我的尝试如下,但我没有得到正确的结果

library(data.table)
# sequence of years
step <- 10
window <- 30
startYear <- -60000+(0.5*window)
endYear <- 0-(0.5*window)
yrSeq <- abs(seq(startYear, endYear, step))

# Example DT
DT <- structure(list(ID = c(2L, 3L, 7L), Age = c(48073L, 49002L, 44297L
), AgeMax = c(49213L, 49638L, 44706L), AgeMin = c(46933L, 48366L, 
43888L)), row.names = c(NA, -3L), class = c("data.table", "data.frame"
))

# split into a list to expand the CI's
s <- split(DT, DT$ID)

# Expand the CI's, to the nearest year in the seq
# merge back into a DT
d_seq <- rbindlist(lapply(s, function(x) {
      data.table(ID = x$ID, Yr = yrSeq[between(yrSeq, x$AgeMin, x$AgeMax)])}))

# remove duplicated years and return min and max years for each ID
d_seq <- d_seq[!duplicated(d_seq$Yr),]
d_seq <- d_seq[, .(AgeMin = min(Yr), AgeMax = max(Yr)), by = ID]

# merge with the original DT and select columns
DT <- merge(DT, d_seq, by = "ID")
DT <- DT[, c(1,2,5,6)]

不幸的是,这不起作用,因为即使 ID == 3 与 ID == 2 重叠(如上所示),也会返回 ID == 3,现在 ID == 2 的 AgeMin 和 AgeMax 不涵盖该 ID 的年龄!

   ID   Age AgeMin AgeMax
1:  2 48073    46935    49205
2:  3 49002    49215    49635
3:  7 44297    43895    44705

我敢肯定我想多了,肯定有一种简单的方法可以满足我的需要 returns,不幸的是我还没有找到任何解决方案。

我已经尝试修改示例 and

这里有一个额外的例子data.table来测试。

testDT <- structure(list(ID = c(54L, 57L, 58L, 60L, 61L, 62L, 64L, 180L
), Age = c(14219L, 13989L, 13883L, 13482L, 13403L, 13383L, 13340L, 
13994L), AgeMax = c(14343L, 14087L, 13972L, 13540L, 13465L, 13442L, 
13407L, 14083L), AgeMin = c(14095L, 13891L, 13794L, 13424L, 13341L, 
13324L, 13273L, 13905L)), row.names = c(NA, -8L), class = c("data.table", 
"data.frame"))

我认为您需要 data.tableigraph 的组合,因为重叠可能会无限链接,即使最后一个 ID 可能与链中的第一个 ID 没有重叠。

这里有一个选项:

#find overlapping intervals using data.table::foverlaps
setkey(setDT(testDT), AgeMin, AgeMax)
d <- unique(foverlaps(testDT, testDT)[, .(x=pmin(ID, i.ID), y=pmax(ID, i.ID))])
    
#find clusters of IDs with overlapping intervals
library(igraph)
g <- graph_from_data_frame(d, directed=FALSE)
m <- setDT(stack(clusters(g)$membership))[, ind := as.integer(as.character(ind))]

#lookup grouping using update join
testDT[m, on=.(ID=ind), cls := values]

#output
testDT[order(ID), .(ID=ID[1L], Age=Age[1L], AgeMin=min(AgeMin), AgeMax=max(AgeMax)),
    cls]

输出:

   cls ID   Age AgeMin AgeMax
1:   3 54 14219  14095  14343
2:   2 57 13989  13794  14087
3:   1 60 13482  13273  13540

编辑: Frank Zhang 的方法让我想起了 中的 David Aurenburg 方法。因此是这样的:

setDT(testDT)[order(AgeMin, AgeMax), g := 
    cumsum(c(0L, (shift(AgeMin, -1L) > cummax(AgeMax))[-.N]))
    ]

testDT[order(ID), .(ID=ID[1L], Age=Age[1L], 
        AgeMin=min(AgeMin), AgeMax=max(AgeMax)),
    g]

这应该会更快。

这是一个data.table解决方案

library(data.table)
setDT(testDT)

testDT[order(AgeMin)
      ][, .(AgeMin=min(AgeMin), AgeMax=max(AgeMax)),
       by=.(group=cumsum(c(1, tail(AgeMin, -1) > head(AgeMax, -1))))]
#>    group AgeMin AgeMax
#> 1:     1  13273  13540
#> 2:     2  13794  14087
#> 3:     3  14095  14343

此解决方案的关键是获取重叠周期的 group

假设我们有两个范围 p1p2。它们的开始和结束命名为 start1,end1,start2end2.

只有两种情况p1p2没有重叠。

  1. start1 > end2
    或者
  2. end1 < start2

由于我们已经Agemin升序排列,所以只需要考虑条件1即可。 然后我们可以使用 cumsum 来获取组标识符。

此解决方案非常易读且灵活,因此请根据您的需要进行调整。

示例数据

testDT <- structure(list(ID = c(54L, 57L, 58L, 60L, 61L, 62L, 64L, 180L
), Age = c(14219L, 13989L, 13883L, 13482L, 13403L, 13383L, 13340L, 
           13994L), AgeMax = c(14343L, 14087L, 13972L, 13540L, 13465L, 13442L, 
                               13407L, 14083L), AgeMin = c(14095L, 13891L, 13794L, 13424L, 13341L, 
                                                           13324L, 13273L, 13905L)), row.names = c(NA, -8L), class = c("data.table", 
                                                                                                                       "data.frame"))

代码

library( data.table )
library( intervals )
#set testDT as data.table
setDT(testDT)
#assuming you want to merge all overlapping intervals to one long interval...
# create a table with joined intervals
# since inertvals need the min-col before max, we switch cols 3 and 4
DT.int <- as.data.table(
  intervals::interval_union( 
    intervals::Intervals( as.matrix( testDT[, 4:3] ) ) , 
    check_valid = TRUE ) )
#set colnames
setnames( DT.int, names(DT.int), c("AgeMin", "AgeMax" ) )
#add interval id's
DT.int[, interval_id := .I ][]
#    AgeMin AgeMax interval_id
# 1:  13273  13540           1
# 2:  13794  14087           2
# 3:  14095  14343           3

#now you can join the intervals back to DT, whatever/however you like
testDT[ DT.int, 
        `:=`( AgeMin.interval = i.AgeMin, AgeMax.interval = i.AgeMax, 
              interval.id = i.interval_id ),
        on = .( AgeMin <= AgeMax, AgeMax >= AgeMin ) ]

输出

testDT

#     ID   Age AgeMax AgeMin AgeMin.interval AgeMax.interval interval.id
# 1:  54 14219  14343  14095           14095           14343           3
# 2:  57 13989  14087  13891           13794           14087           2
# 3:  58 13883  13972  13794           13794           14087           2
# 4:  60 13482  13540  13424           13273           13540           1
# 5:  61 13403  13465  13341           13273           13540           1
# 6:  62 13383  13442  13324           13273           13540           1
# 7:  64 13340  13407  13273           13273           13540           1
# 8: 180 13994  14083  13905           13794           14087           2

现在 process/summarise 更进一步,例如:获取 interval.id...

的非重复行
testDT[ !duplicated( interval.id ), .(ID, Age, AgeMax, AgeMin) ]
#    ID   Age AgeMax AgeMin
# 1: 54 14219  14343  14095
# 2: 57 13989  14087  13891
# 3: 60 13482  13540  13424