抽出による文字列分離(2)_書式の理解.R
要約
・抽出するには書式を理解
・stringr::str_match_all()で揃えたい部分を抽出
・str_pad()で桁をそろえる
・リストへの関数適用なのでapply系かfor文か
FIの数字部分の桁数は結構異なる
前の記事の繰り返しになるのだけれども、FIという分類コードは、実はデータベースごとで異なる書式にされていることが多い。例えば検索に使う際にも入力書式が違っているので、極端な話、"G06Q001"を"G06Q1"と解釈するデータベースもあれば、"G06Q10"になるデータベースもある。
そのせいなのか、5桁以降の数字(サブクラスという)に1を設定する場合には、10は設定せずに11を使うようになっているようだ。誤って10で認識された場合に検索結果としての件数が0件になるので、誤ったことが分かるんだろう。とはいえ、階層検索の際にG06Q01*のようにしてしまうと出たり出てこなかったりとまたややこしい話があるのだが。
それはともかく、例えば「桁だけで区切れるし、内部処理も桁だけで処理する方が楽だから、もう"/"なんか要らないんじゃね?」ってオミットしてしまったデータベースもある。
そんな感じなので、データベース間での書式の違い、特に出力書式の違いというのが結構問題になったりする。
各データベース(とその上にのっかったソフトウェア)によって出来ること出来ないことというのが結構異なる。実務上は複数を使い分けることになるのだけど、実のところ業界標準というものがない。まあ、グローバルで統一もできないだろうし、今後もそうはならないだろう。
かといって、日本の特許庁が委託しているJ-PlatPatが吐き出す書式こそが日本では正しくて、日本でサービスを行う各ベンダーもそれに合わせなければならない、などということは個人的にもまったく思っていないし、事実そうなっていない。扱いにくいデータを整えて付加価値を付けてくれるのであれば、その方が好ましいのは間違いない。
何にせよ、それぞれの出力を統合してひとつにして仕事をしていく必要がある限りは、各データベースごとの出力書式を自分で揃えてやる必要が出てくる。
そのためには事前に書式は確認しておかないといけないというのが、データベースを利用する際の"常識"ともいえる。
ちなみに公式の書式としては下を参照するとよい。公式は。
揃えるべき部分の特定と抽出
まずは前回使った正規表現を確認する。
[A-H]\\d{2}[A-Z]\\d{2,4}/\\d{2,5}(,\\d{3})?(@[A-Z])?
前回は「本体部分」+「補足部分」と言っていたのだけれど、正しくは「IPC記号」+「展開記号」+「分冊識別記号」だ。
IPCとは国際的に決められた特許の分類のことであって、FIはそれをさらに日本の特許庁(特許庁は各国にある)が細分化して使い勝手を良くしたものになる。
その使い勝手の部分の一つが「展開記号」と「分冊識別記号」だ。「展開記号」が例の厄介な",100"のようなコンマ付きで、「分冊識別記号」が"@A"のことを指す。
実はさらに「ファセット分類記号」というのがあって、3文字アルファベットがさらに付くこともある。
ちなみに前回使った正規表現では、ファセット分類記号は無視している。理由? えーっと、自分の業務範囲ではあまり出てこないからかな……。
という長い前置きをしたところで、実際に前回の正規表現を上記の分け方で逐次確認していく。
まずは簡単なところからで、「展開記号」と「分冊識別記号」だ。
「展開記号」は、先ほどのリンクをみると「3桁の数字」が使用されることになっている。コンマまでは入っていない。じゃあなんで入れたし。
「分冊識別記号」は、同様に「アルファベット1文字」が使用されることになっている。@は許す。分けやすいからめっちゃ許す。
よく見ると、「I」と「O」が除かれているのだが、まあこれは数字の1や0との混同を避けるためだろう。アルファベットで表記する他の部分もそうなっていそうな気がするので、あえて何か複雑なことをする必要はとりあえずないだろう。広域ファセットくらい?だけど別にまあ支障はないと思われる。
「IPC記号」をさらに細かく
IPC記号の部分は、大きく5つに分かれている。セクション、メイン/サブクラス、メイン/サブグループだ。
とはいえ、先頭4桁(セクション+メイン/サブクラス)までは、経験上はどれを見ても固定になっている。問題はその後のメイン/サブグループだ。
メイングループやサブグループは、技術の発展とともに色々と細分化されたりその逆が起きたりしている。それに伴って桁数も変化していて、メイングループは3桁、サブグループは4桁まではあるらしい。ひょっとしたら欧米版FIであるCPCではもっと多いかもしれない。そんなだから、メイン/サブグループの数字を予め+1桁ずつ多く4桁・5桁と確保して空白やゼロで桁揃えをしているデータベースもあれば、単純に桁揃えをせずにそのままつないでいるものもある。
そのままつなぐのは(何も考えなくて済むから書く側としては)楽なのだけど、一方でそれを機械で読み取る側からしたら多少面倒になる。
桁が揃っていれば、○桁目~×桁目まではメイングループで、と簡単に読み取れる。そうでないJ-PlatPatの出力みたいなのだと、スラッシュの位置を判別して、5桁目からスラッシュの1つ前までがメイングループで、となる。ほらめんどくさい。
そうはいってもお仕事なので、まあ、さらにその後の作業が楽になりそうな方向へ書式をそろえるということをしたい。ここでは桁を揃えておく方が楽そうなので、そちらに合わせることにする。
処理の方針を立てる
1.マルチアンサーのFIから各FIを抽出・分離する
2.各FIをさらに各構成要素に分解する
3.各構成要素を適切な桁に直す
4.再び結合してFIにする
5.各FIを結合しなおしてマルチアンサーにする
ここで1と2は、ひとつの関数で行ける。
str_match_all()で、予め各構成要素をグループ化しておくと、グループごとに分離した結果をマトリックスにして出力してくれる。グループ化とは、上の正規表現の「(,\\d{3})?」で使ったように、丸カッコで囲った部分を1つのグループとして見てくれるというものだ。ただし抽出の際には正規表現全体とマッチするものを抽出してくれる。その上でグループ化して分割してくれる。試しに見てみよう。
> df$FI %>% head(3) %>% str_match_all("([A-H]\\d{2}[A-Z])(\\d{2,4})/(\\d{2,5})")
[[1]]
[,1] [,2] [,3] [,4]
[1,] "G06Q20/06" "G06Q" "20" "06"
[[2]]
[,1] [,2] [,3] [,4]
[1,] "E04F13/10" "E04F" "13" "10"
[2,] "E04F13/08" "E04F" "13" "08"
[3,] "E04F15/04" "E04F" "15" "04"
[[3]]
[,1] [,2] [,3] [,4]
[1,] "A41D13/00" "A41D" "13" "00"
とりあえず、(先頭4桁)(メイングループ)/(サブグループ)の3つに分けてみた。全体をマッチさせた結果が1列目に、各グループ分けの結果が2列目以降に格納される。サブグループは数字だけにしてスラッシュはグループから外してあるので、スラッシュは1列目だけにしか登場しない。また元の要素ごとにリスト化されていて、元の要素に含まれるFIが1つであれば結果はベクタになるけれども、FIが複数あるとマトリクスになってリストに格納されることになる。
数字とはいえここでは文字列型だし、桁をそろえるならstringr::str_pad()が使えそうだ。
とはいえリストへ適用する必要があるので、lapply()などを使ってリストに関数を適用する必要がありそうだし、さらにその中のベクタやマトリクスの行単位で関数を適用するように、apply系関数を入れ子にしていかなければいけないかもしれない。
もしくはオーソドックスにfor文を使う方が簡単になるかもしれない。
さらにそれらの出力結果をベクタで受け取るのかリストで受け取るのか。
ここから先は実際に手を動かして確認した方が早そうだ。
今日はここまで。