簡體   English   中英

計算與相對於當前行的條件匹配的行

[英]Count rows matching a criteria relative to current row

我有一個像這樣結構化的數據幀(但它實際上有~400k行):

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且其結束大於第一行start。

我試着用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

這正是我的預期!

編輯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的注釋


我最初試圖使用第二個連接將結果附加到原始df失敗,以防OP指出的相同id計數不同。 這可以通過在第二個連接中包含行號來修復:

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 

說明

非equi連接中的連接條件可以解決問題。 如果第一個間隔在第二個間隔開始之前結束或者第二個間隔在第二個間隔結束之后開始,則兩個間隔重疊,

e 1 <s 2 OR e 2 <s 1

現在,如果兩個間隔相交/重疊然后上述相反必須是真實的。 通過否定和應用德摩根定律,我們得到了條件

s 2 <= e 1 AND e 2 > = s 1

用於非equi連接

數據

OP的擴展數據集如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")

我最初誤解了這個問題,我認為@Uwe的方法是要走的路。 在我的第一個回答中,我使用data.table來識別每個id的后續日期的組(以及組中的行數),顯然不是你所追求的。

這里還有一個簡短的sqldf片段來補充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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM