特許のネットワーク分析(3).R
要約
・str_splitでマルチアンサーの分解結果をリストに詰める
・mapでリストを処理
・最後はrlist::list.stackで1つのデータフレームに
前回のお話
改めて方針を立て直す
半年も放置していると、さすがに忘れるのである。
無向グラフ用に、隣接行列またはエッジリスト(n行2列)を作るのが当面の目標になる。
エッジリストに入れるノードは、マルチアンサーを分解した各要素であるし、1つのマルチアンサーに入っていた各要素の間にエッジが作られるようにしたい。
すると、マルチアンサーが「A|B|C|D」のようになっていた場合には、A~Dの4要素について、4C2 = 6個のペアを生成して各ペアの間にエッジが作られるようになっていればよい。(当然、有向グラフなら4P2 = 12個のペアを生成する必要がある。)
すると、nC2の組合せを作る関数と、その関数に送るために、マルチアンサーの各要素をまとめたベクタが必要になる。
前者は、conbn(x, m = 2)とすれば済むのだけれど、後者のためにはまずマルチアンサーをどう分解するか、そしてどうやってベクタを取り出すかの検討が必要だ。分解するとなるとtidyr::separate()やstringr::str_split()が思いつくのだが、そういえばstr_splitは分解結果をlistにしてくれるんだった。listに詰めるというのも一つの手かもしれない。
まずはマルチアンサーをseparate()
> hold.d <- df %>% separate(権利者, into = paste0("H", 1:3), sep = ",") %>%
+ select(starts_with("H"))
今回は予め権利者列に入っている出願人の数の最大値が3だと分かっていたので、H1~H3までの3列作っている。
各出願人ごとに分割された結果はH1~H3列に格納されているので、select()でそれらを抽出してhold.dに格納している。
よく考えてみたら、最大値を数えるときにセパレータ(sep)の数で数えていたので、数をhoge列に入れておけばfilter(hoge > 1)して共願のものだけ抽出することもできたんだなあ。失敗失敗。
それはともかくも、あらかじめ列数を決めておかなければいけないというところが、どうにも見通しが悪い。
じゃあseparate_rows()か?
> df
ROW HOLDER
1 1 A,B,C,D
2 2 A,E,F
3 3 B
4 4 C,F
> df %>% separate_rows(HOLDER, sep = ",")
# A tibble: 10 x 2
ROW HOLDER
<int> <chr>
1 1 A
2 1 B
3 1 C
4 1 D
5 2 A
6 2 E
7 2 F
8 3 B
9 4 C
10 4 F
列数をあらかじめ指定しなくてよい気軽さがあるとはいえ、ROWで順次フィルタしながらHOLDER列をpullしてベクタを取り出して……というのもまあできなくはない。
だったらstr_split()か
# こんなデータフレーム
> df
HOLDER
1 A,B,C,D
2 A,E,F
3 B
4 C,F
# こんな感じにリストに詰められる
> df$HOLDER %>% str_split(",")
[[1]]
[1] "A" "B" "C" "D"
[[2]]
[1] "A" "E" "F"
[[3]]
[1] "B"
[[4]]
[1] "C" "F"
うまくリストに詰めてくれる。あとはリストの各要素にあるベクタに対して、それぞれcombn(m = 2)で処理していけばいい。
当然ながら単願は要素が2以上にならないので、combn(m = 2)としたときにエラーが起きる。その辺りはデータフレームの時点で、例えばマルチアンサーのセパレータの有無でfilterするなどして除いておけばよい。
ここまでをまとめるとこうなる。
# こんなデータフレーム
> df
HOLDER
1 A,B,C,D
2 A,E,F
3 B
4 C,F
# まずは要素が2以上のもの=セパレータがあるものだけに
> df %>% filter(str_detect(HOLDER, ","))
HOLDER
1 A,B,C,D
2 A,E,F
3 C,F
#
> df %>% filter(str_detect(HOLDER, ",")) %>% pull(HOLDER) %>% str_split(",")
[[1]]
[1] "A" "B" "C" "D"
[[2]]
[1] "A" "E" "F"
[[3]]
[1] "C" "F"
# 参考までにsimplify = Tにするとこうなる
> df %>% filter(str_detect(HOLDER, ",")) %>% pull(HOLDER) %>%
+ str_split(",", simplify = T)
[,1] [,2] [,3] [,4]
[1,] "A" "B" "C" "D"
[2,] "A" "E" "F" ""
[3,] "C" "F" "" ""
pull(HOLDER)なり.$HOLDERとしておかないとstr_split()が通してくれないところが地味にいやらしい。
次はpurrr::map()
リストに詰めることができたので、ここからはmapの出番だ。functionを準備しておいて、リストの各要素に対して処理を行い、リストで返してもらう。
なおdata.frameに対して処理をする場合には、purrr::ではなくpurrrlyrが望ましいとのことだ。
> df %>% filter(str_detect(HOLDER, ",")) %>% pull(HOLDER) %>%
+ str_split(",") %>%
+ map(function(x){combn(x, m=2) %>% t()})
[[1]]
[,1] [,2]
[1,] "A" "B"
[2,] "A" "C"
[3,] "A" "D"
[4,] "B" "C"
[5,] "B" "D"
[6,] "C" "D"
[[2]]
[,1] [,2]
[1,] "A" "E"
[2,] "A" "F"
[3,] "E" "F"
[[3]]
[,1] [,2]
[1,] "C" "F"
HOLDER列をpullしておいてstr_splitに与える。結果はlistに詰められているので、mapを使ってリストの各要素xにcombn(x, m=2) %>% t()としてやる。
最後は rlist::list.stack()
rlist::list.stack()は、listの各要素をスタックされて1つのデータフレームにしてくれる……ので、あらかじめmapの中のfunctionにas.data.frame()を入れておく必要があるんだな。しまった。
> df %>% filter(str_detect(HOLDER, ",")) %>%
+ pull(HOLDER) %>% str_split(",") %>%
+ map(function(x){combn(x, m=2) %>% t() %>% as.data.frame()}) %>%
+ list.stack()
V1 V2
1 A B
2 A C
3 A D
4 B C
5 B D
6 C D
7 A E
8 A F
9 E F
10 C F
# filterの代わりに、rlist::list.filter()を使うとこうなる。
> df$HOLDER %>% str_split(",") %>% list.filter(length(.) > 1) %>%
+ map(function(x){combn(x, m=2) %>% t() %>% as.data.frame()}) %>%
+ list.stack()
V1 V2
1 A B
2 A C
3 A D
4 B C
5 B D
6 C D
7 A E
8 A F
9 E F
10 C F
最終的には意外とシンプルに。
途中で食わせる値の型については注意が必要になってしまったが、それはそれで勉強になった……かもしれない。
list.filter()はなんだか便利そうだ。さすがrlist。
以上。