2017-05-31 10 views
3

data.tableを使用して兄弟ネットワークを作成します。data.tableを使用する兄弟ネットワーク

私のデータは

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 

3つの列は、それぞれ、母の父とIDのIDをIDを表している。この

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

のように見えます。 0またはNAは利用できません。したがって、上記のデータでは、3人と4人は完全兄弟です(両者とも父親1と母親2)。一方、12歳と13歳は半分の兄弟です(父親は同じですが母親は6)。

データフレームの各行について、私はその人の兄弟のリストを望んでいます(半分の兄弟から始めてみましょう)。私の理想的な最終結果は最後の列、sibsは、リストまたはベクトルである(そして、それは、データセットの一部である必要はありません)

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 

だろう。基地Rを使用して出力を得るため

粗バージョンは兄弟がそれぞれのIDを介して実行取得する

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

以下に示します。これは、所望の出力だった

​​

を生成

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) 
に彼らの父と母を見つけて、以前のリストから2つの関連エントリを兼ね備えています。上のコードは速くてもかわいいものではありません。私は実際には 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それがインデックスではなくラベルを含む以外は、上記まさにmoffspringのようなこの

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

のように見えます。しかし、それは実際問題ではありません。その後、私は一緒に

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で3つの異なる提案を実行し、3つのアプローチの間にタイミングの違いがあるかどうかを確認しました。 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

setdiff()union()と組み合わせてmapply()を使用する方法があります。リストに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によってindataindataをマージするrbindlistそれらをです。 id.ytmp3は、兄弟の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] 
012あなたの分析が完了しない限り、 DTにこれらの列を追加する

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 

を与える

は逆効果です。私は有用な分析は、さまざまなテーブルに含まれている非リストの列にあると思います。

+1

私は、データテーブルのリストを維持するにはもっと同意できませんでした。私はこのアプローチが最速ではないことに少し驚いています。すべてのコードがほんの一行分のコードに含まれていたことをどうぞよろしく! – ekstroem