【问题标题】:Merging rows with overlapping values合并具有重叠值的行
【发布时间】:2023-03-12 20:20:01
【问题描述】:

我有一个data.table,它的每一行、一个年龄列和 2 列都有唯一ID,其中包含年龄的置信区间。我想要做的是合并具有重叠 CI 的行,因此返回的 CI 是重叠的最小/最大值。

   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 == 2 的 AgeMin 和 AgeMax 不包括年龄那个ID!

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

我确定我想太多了,必须有一种简单的方法可以返回我需要的东西,不幸的是我找不到任何解决方案。

我已经尝试修改示例herehere

这是一个额外的示例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"))

【问题讨论】:

    标签: r merge data.table overlap


    【解决方案1】:

    这是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 来获取组标识符。

    【讨论】:

      【解决方案2】:

      我认为您需要 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 的方法让我想起了 How to flatten / merge overlapping time periods 中的 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]
      

      这应该更快。

      【讨论】:

        【解决方案3】:

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

        样本数据

        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
        

        现在可以根据需要进一步处理/总结,例如:获取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
        

        【讨论】:

          猜你喜欢
          • 2014-12-11
          • 2021-09-12
          • 2020-05-19
          • 1970-01-01
          • 2023-03-23
          • 1970-01-01
          • 2021-05-29
          • 1970-01-01
          • 2018-11-23
          相关资源
          最近更新 更多