2017-05-31 13 views
3

我想用data.table創建一個兄弟網絡。使用data.table的兄弟網絡

我的數據是這樣的

indata <- 
structure(list(id = c(1L, 2L, 3L, 4L, 12L, 13L, 14L, 15L), fid = c(NA, 
9L, 1L, 1L, 7L, 5L, 5L, 5L), mid = c(0L, NA, 2L, 2L, 6L, 6L, 
6L, 8L)), .Names = c("id", "fid", "mid"), class = "data.frame", row.names = 
c(NA, -8L)) 

這是

id fid mid 
1 1 NA 0 
2 2 9 NA 
3 3 1 2 
4 4 1 2 
5 12 7 6 
6 13 5 6 
7 14 5 6 
8 15 5 8 

三列分別代表ID,母親的父親和id的ID。 0NA表示不可用。因此,在上面的數據中,人3和4是全兄弟姐妹(他們都有父親1和母親2),而12和13是半兄弟姐妹(他們有不同的父親,但是同母親,6)。

對於數據框中的每一行,我想要一個人的兄弟姐妹列表(讓我們先考慮一下兄弟姐妹)。我理想的最終結果將是

id fid mid sibs 
1 1 NA 0 NA 
2 2 9 NA NA 
3 3 1 2 4 
4 4 1 2 3 
5 12 7 6 13, 14 
6 13 5 6 12, 14, 15 
7 14 5 6 12, 13, 15 
8 15 5 8 13, 14 

在最後一列,sibs,是一個列表或向量(和它不必是數據集的一部分)。

粗版本,以獲得下面

# get a list of offspring for each father id 
foffspring <- by(indata, indata$fid, function(i) { i$id }, simplify=FALSE) 
# and mother id 
moffspring <- by(indata, indata$mid, function(i) { i$id }, simplify=FALSE) 

在使用輸出基礎R被賦予要獲得的兄弟姐妹通過每個ID運行。找到自己的父親和母親,並從以前的列表

sibs <- sapply(1:nrow(indata), function(i) { 
    res <- c() 
    if(!is.na(indata$fid[i])) 
     res <- c(res, unlist(foffspring[paste0(indata$fid[i])])) 
    if(!is.na(indata$mid[i])) 
     res <- c(res, unlist(moffspring[paste0(indata$mid[i])])) 
    unique(res[res != indata$id[i]]) 
    }, simplify=TRUE) 

結合兩種相關的條目這將產生

​​

這是所需的輸出。現在上面的代碼不是很快或很漂亮,我真的很想看看我能不能看到一個漂亮的data.table版本。但是,我的data.table-fu似乎缺乏。

library(data.table) 
DT <- data.table(indata) 
# Create lists with the _indices_ of the offsprings 
FT <- DT[ , list(yidx = list(.I)) , by = fid ] 
MT <- DT[ , list(yidx = list(.I)) , by = mid ] 

MT看起來像這樣

mid yidx 
1: NA  2 
2: 0  1 
3: 2 3,4 
4: 6 5,6,7 
5: 8  8 

酷似moffspring以上不同在於它含有的指數,而不是標籤。但是,這並不是一個真正的問題。然後,我想表合併到一起

setkey(DT, fid) 
setkey(FT, fid) 
setkey(MT, mid) 

# Inner join 
P1 <- DT[FT] 

# And inner join on mother 
setkey(P1, mid) 
P1[MT] 

,現在最終的結果看起來是這樣的

id fid mid yidx i.yidx 
1: 2 9 NA  2  2 
2: 1 NA 0  1  1 
3: 3 1 2 3,4 3,4 
4: 4 1 2 3,4 3,4 
5: 13 5 6 6,7,8 5,6,7 
6: 14 5 6 6,7,8 5,6,7 
7: 12 7 6  5 5,6,7 
8: 15 5 8 6,7,8  8 

這是幾乎那裏。現在,如果我採用yidxi.yidx的行式聯合,那麼我可以得到半同胞(包括人自己)的列表,並且行式相交可以產生完整的兄弟姐妹。請注意,這裏的指數是指DT中的指數,而不是最終的data.table,但也可以固定。

但是,我有一種嘮叨的感覺,像這樣的東西可以在幾行data.table代碼和「溫柔的一隻手」中更有效地完成。任何人都可以將我指向正確的方向嗎?

[對不起,超長帖]


更新基於下面的答案。爲了它的樂趣,我通過microbenchmark運行了三種不同的建議,看看這三種方法之間是否存在時間差異。 f1()是@Frank的建議,f2()是@mtoto給出的解決方案,而f3是@ amatsuo_net的方法。嘗試向量的長度爲1000,這裏是輸出

Unit: milliseconds 
expr  min  lq  mean median  uq  max neval cld 
f1() 4020.8112 4387.7950 4614.7896 4498.8043 4770.1184 6837.672 100 c 
f2() 656.9575 685.7706 727.5191 710.3003 735.2832 1080.423 100 a 
f3() 1637.8927 1706.7528 1789.1794 1739.4428 1814.7776 2403.474 100 b 

在方法上有相當大的差異。我需要通過一個擁有700萬個ID的數據集來運行它,這樣肯定會有明顯的影響。謝謝大家!

回答

1

下面是使用mapply()結合setdiff()union()的方法。收集id的成列表後,我們首先排除電流id,然後union()名單從兩側:

setDT(indata)[,msib:=.(list(id)), by = "mid"][ 
    ,msibs := mapply(setdiff, msib, id)][ 
    ,fsib := .(list(id)), by = "fid"][ 
    ,fsibs := mapply(setdiff, fsib, id)][ 
    ,sibs := mapply(union, msibs, fsibs)][ 
    ,c("msib","msibs", "fsib", "fsibs") := NULL] 
> indata 
# id fid mid  sibs 
#1: 1 NA 0   
#2: 2 9 NA   
#3: 3 1 2  4 
#4: 4 1 2  3 
#5: 12 7 6 13,14 
#6: 13 5 6 12,14,15 
#7: 14 5 6 12,13,15 
#8: 15 5 8 13,14 
+0

偉大的解決方案。驚人的快! – ekstroem

1

我會做這樣的事情。

library(data.table) 
library(dplyr) 
setDT(indata) 
tmp <- merge(indata, indata[, 1:2], by = "fid", allow.cartesian = TRUE) 
tmp2 <- merge(indata, indata, by = "mid", allow.cartesian = TRUE) 
tmp3 <- rbindlist(list(tmp,tmp2), fill = T) 
dt_siblings <- tmp3[id.x != id.y, unique(id.y) %>% sort() %>% paste(collapse = ", "), by = id.x][order(id.x)] 
setnames(dt_siblings, 'id.x', 'id') 
setnames(dt_siblings, 'V1', 'siblings') 
outdata <- merge(indata, dt_siblings, all.x = T) 

的想法是通過fidmid然後rbindlist它們合併indataindatatmp3中的id.y列是兄弟姐妹的ID(由於完全兄弟姐妹而有重複)。在下一行中,刪除重複,排序,然後連接。輸出看起來像這樣:

> outdata 
    id fid mid siblings 
1: 1 NA 0   NA 
2: 2 9 NA   NA 
3: 3 1 2   4 
4: 4 1 2   3 
5: 12 7 6  13, 14 
6: 13 5 6 12, 14, 15 
7: 14 5 6 12, 13, 15 
8: 15 5 8  13, 14 
2

我會阻止列表列儘可能長。

與兄弟姐妹開始,這裏有一個簡單的方法:

sibDT = DT[!is.na(fid) & !is.na(mid), 
    CJ(id = id, sid = id)[id != sid] 
, by=.(fid, mid)] 

# fid mid id sid 
# 1: 1 2 3 4 
# 2: 1 2 4 3 
# 3: 5 6 13 14 
# 4: 5 6 14 13 

,然後定義一半的兄弟姐妹共享父,但在sibDT沒有出現:

hsibDT = melt(DT, id = "id")[!is.na(value), 
    CJ(id = id, hsid = id)[id != hsid] 
, by=.(ptype = variable, pid = value)][!sibDT, on=.(id, hsid = sid)] 

# ptype pid id hsid 
# 1: fid 5 13 15 
# 2: fid 5 14 15 
# 3: fid 5 15 13 
# 4: fid 5 15 14 
# 5: mid 6 12 13 
# 6: mid 6 12 14 
# 7: mid 6 13 12 
# 8: mid 6 14 12 

我會停在這裏,但用列表或字符列瀏覽結果...

DT[sibDT[, .(sibs = toString(sid)), by=id], on=.(id), sibs := i.sibs, by=.EACHI ] 
DT[hsibDT[, .(hsibs = toString(hsid)), by=id], on=.(id), hsibs := i.hsibs, by=.EACHI ] 

# or... 

DT[ 
    rbind(sibDT[, .(id, oid = sid)], hsibDT[, .(id, oid = hsid)])[, 
    .(asibs = toString(oid)) 
    , by=.(id)], 
    on = .(id), 
    asibs := i.asibs 
, by = .EACHI] 

這給

id fid mid sibs hsibs  asibs 
1: 1 NA 0 NA  NA   NA 
2: 2 9 NA NA  NA   NA 
3: 3 1 2 4  NA   4 
4: 4 1 2 3  NA   3 
5: 12 7 6 NA 13, 14  13, 14 
6: 13 5 6 14 15, 12 14, 15, 12 
7: 14 5 6 13 15, 12 13, 15, 12 
8: 15 5 8 NA 13, 14  13, 14 

添加這些列DT是適得其反的,除非你的分析是完整的。我想任何有用的分析都將在各種表格中包含的非列表列中進行。

+1

我不能同意在保持數據表的名單。我有點驚訝這種方法不是最快的。喜歡它如何包含在一行代碼中! – ekstroem