動機
住所が与えられていて、それから自治体を抽出したいということはよくあると思う。でも住所のパースはそんなに簡単な作業ではないし、そこに誤記が含まれていたりするとかなり大変。ここではOpenRefineを使ってwikidata上の情報を参照しながらパースしていく手順を、覚え書きとして残しておく。これが決定版というわけではないけれど、使っている正規表現はOpenRefineではなくスクリプトとかでパースする場合にも参考になるかもしれない。
目的
都道府県から始まる住所から基礎自治体(市町村+特別区)1を抽出し、wikidataのIDにマッピングする。
今回はwikidataへのReconciliationを目的としているためかなり煩雑になっているが、誤字の検出やIDの対応付けが不要な場合にはReconcileする必要はなく、正規表現による切り出し4回でパース自体はできる。
注意事項
- wikidataのデータが書き換わることで上手く処理できなくなる可能性がある
- 一般論として都道府県は一意な文字列になるが、市町村は一意な文字列にならない。たとえば「府中市」には東京都府中市と広島県府中市があるので、都道府県と組み合わせた状態でなければこの2つを区別できない(同一名称の市区町村一覧を参照)2。この手順ではwikidataに対してReconciliationすることで別のIDとして区別されるようになっている。
処理の流れ
- 都道府県を抽出する
- 郡を抽出する
- 郡部にある町村を抽出する
- 郡部以外にある市区町村3を抽出する
手順
実例として文部科学省学校コードの令和4年暫定版CSVファイル(東日本)を用いる。東日本だけになるが津々浦々の学校の住所が羅列されているデータセットである4。最初の行にメタデータ(といってもタイトルと更新日だけ)が入っているので1行無視して読み込む。
都道府県
"Add column based on this column"で都道府県名を切り出す。
カラム名はprefLabel
として以下のGREL式を使う。都道府県名が省略されているなどでマッチしない場合はnullになる。
value.find(/^(.{2}[都道府県]|.{3}県)/)[0]
ここで.{2,3}[都道府県]
とまとめてしまうと、山梨県都留市、広島県府中市などで不正なパースになる。
後で使うのでprefLabelカラムをQ50337 (prefecture of Japan)に対してReconcileしておく。
パースさえできれば十分で、誤字の検出やIDへの対応付けが不要な場合にはReconcileする必要はない。以下同じ。
ここでマッチしないものがある場合、都道府県名の省略・誤字や旧自治体名などが原因なので個別に修正が必要。
郡
同様に以下の式を使い郡名をgunLabelカラムに切り出す。
value.replace(cells.prefLabel.value,"").find(/^(中郡|.[^和]郡|[^市区]{3,4}郡)/)[0]
蒲郡市・小郡市、大和郡山市、丸亀市郡家町などを正しくパースするために3分割している。これはそれぞれ、1字+郡は「中郡」のみ、2字+郡は2字目に「和」が存在しない、郡名に「市」や「区」は含まれない、という知識を利用している。このあたりは自治体合併がある度にチェックが必要。
Facet by blankを設定しfalseを選択する。
Facetでfalseを選択した状態でgunLabelカラムをQ1122846 (district of Japan)に対してReconcileする。このときprefLabelカラムをプロパティP131として含める。
含めないと愛知県海部郡と徳島県海部郡のような同字郡を区別できず、以下の確認作業に余計な手間がかかる。
wikidata上のデータ構造の関係で、郡名へのReconciliationは一意に定まらないことが多く、"gunLabel: judgment"のFacetでnoneを選択した状態で個別に選んでいく必要がある。
- 北海道では郡と道の間に総合振興局が挟まっている場合が多く、そのような郡名は完全マッチしない。
-
- 通常はトップヒット(スコア100)のものを選べば良い
- 島根県隠岐郡も隠岐支庁のせいでトップヒットにならないので注意
作業を終えたら"gunLabel: judgment"のFacetを解除する。
郡部の町村
※東京都島嶼部の町村は郡がないのでここでは扱わない
Facetでfalseを選択した状態で以下の式を用いてcityLabelカラムに切り出す。
value.replace(cells.prefLabel.value,"").replace(cells.gunLabel.value,"").find(/^([^大玉][町村]|[大玉][町村]町|.[^町村]{1,4}[町村])/)[0]
大町町および玉村町を正しくパースするため正規表現を3分割している
これまで同様にcityLabelカラムをQ1054813 (municipality of Japan)に対してReconcileする。
このときgunLabelカラムをプロパティP131として含める。
郡名同様、wikidata上のデータ構造の関係で、町村名へのReconciliationは一意に定まらないことがある。"cityLabel: judgment"のFacetでnoneを選択した状態で個別に選んでいく必要がある。
- とくに真狩村・釧路町は正解がトップヒットではないので注意が必要
作業を終えたら"cityLabel: judgment"のFacetを解除する。
郡部以外の市区町村
※東京都島嶼部の町村は郡がないのでこちらで扱う
gunLabelのFacetでtrueを選択した状態で以下の式を用いてcityLabelカラムに切り出す。すでにcityLabelカラムは存在しているので、"Transform"を使う方が手数が少なくて済む。
cells.学校所在地.value.replace(cells.prefLabel.value,"").find(/^(市[川原]市|[四廿野][日々]市市|[^市区]{1,6}市|[北港]区|[^市区町村]{2,3}[区町村])/)[0]
前半は市川市・市原市・四日市市・廿日市市・野々市市への対策込みの市、後半は北区・港区への対策込みの東京都特別区および島嶼部5
同様にcityLabelカラムをQ1054813 (municipality of Japan)に対してReconcileする。
このときprefLabelカラムをプロパティP131として含める。
とくに北海道や東京都島嶼部で顕著だが、Reconciliationで一意に定まらないことがあるのもこれまでと同様である。
結果
ずいぶん煩雑だと感じる人が多いのではないかと思うが、Reconciliationはせずに正規表現で抽出するだけならかなり便利に使える。Reconciliationのところは、wikidataではなく自前で専用のデータセットを用意しておけばぐっと簡便になるはずである。
今回の実例では33,308件中最終的に10件について自治体へのマッピングができなかった。いずれも東京都島嶼部で、全国的にみると一般的ではない表記(○○郡に相当する場所が○○島となっている)が使われていたためである。
なお同様に西日本のデータセット(令和4年暫定版CSVファイル(西日本))を用いると、23,631件のうち80件がマッピング不能で、うち78件が「宝塚市」が文字化けしたことによるもの、あとは「岡山県岡山県」と「愛媛県今冶市」という誤字によるものであった。