QGISのテーブルをファジー結合してみる

# QGISのテーブルをファジー結合してみる

## はじめに

OSGeo Japan Discussの質問で、「文字列が一致していない場合に、テーブル結合が出来るのか」といった質問があったので、探してみました。QGIS 3.8以降に実装された機能でできそうだというこことで、説明を作ってみました。
以下のページの記述を少し変えて、作りました
[Fuzzy Table Joins in QGIS](https://spatialthoughts.com/2019/09/26/fuzzy-table-joins-in-qgis/)


## 使用データ

[国土数値情報ダウンロードサービス](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-v2_3.html#prefecture08)から入手した、H31年時点の茨城県の行政界（GeoJSON形式）と、[茨城県Webページ](https://www.pref.ibaraki.jp/kikaku/tokei/fukyu/tokei/betsu/jinko/getsu/jinko2008.html)からコピペした、茨城県市町村別人口データです。データは、[ここから入手](https://www.dropbox.com/s/dpo6613qx6jwbh1/ibaraki_pop.zip?dl=0)できます。

GeoJSONのデータは、以下の通り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/5aec3958-c639-0c92-1d1c-992e67f211ad.png)
N03_004 というカラムに市町村名が入っています。

人口データは、CSVにしました。テキストエディタでみると、以下の通り。わざと、市町村名の後に全角スペース、名前と市の間に全角や半角のスペース、市の削除、市町村名の間に半角スペース挿入等々、色々やっています（テストという意味も含めて）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/6faf95d4-4f5d-7047-1c14-7e5c8053ceef.png)
このデータをQGISに読み込んで、GeoJSONのN03_004とCSVの市町村をkeyにして結合、人口を元に色分けすると以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/6921e1ab-0728-b075-9739-a483c876d5fe.png)
全然結合しません。

さてここで、CSVファイルの方がおかしいので、こちらを修正し、ちゃんと結合できるかどうか、ためします。まず、csvファイルをQGIS上の属性テーブルとして開き、フィールド計算機をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/866c4b08-bb11-0bfb-78a9-50da4566b0fd.png)
ここで、「新しいフィールドを作る」、「仮想フィールド作成」にチェックを入れ、式の部分に以下のテキストをコピペします。

``` 
array_first(aggregate(
layer:= 'ibaraki_admin',
aggregate:='array_agg',
expression:=N03_004,
filter:=levenshtein(N03_004, attribute(@parent, '市町村')) <= 4,
order_by:=levenshtein(N03_004, attribute(@parent, '市町村'))))
```
スクリーンショットは、以下の感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/5b23cec9-1463-686a-aec7-74040a958a65.png)


細かく説明すると、

```layer:= 'ibaraki_admin',``` の部分は、csvと結合させたいターゲットレイヤ名
```expression:=N03_004,``` の部分は、ターゲットレイヤの中の、結合ターゲットとなるフィールド名

```filter:=levenshtein(N03_004, attribute(@parent, '市町村')) <= 4, order_by:=levenshtein(N03_004, attribute(@parent, '市町村'))```

の部分で、ターゲットのN03_004に含まれる市町村名と、csvファイルの市町村名を比較し、半角で4文字（全角だと2文字）文字があってれば、その合っている文字を結果として返す、という処理を行っているとおもいます、多分 [^1]。

これでOKをクリックし、処理が終了すると、結合対象のadminの市町村名と同じ記述が、fuzzyのカラムに入力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/fec1c6e4-f5a8-b495-bbff-e2dd7e7be71e.png)
ここで、結合がすんだcsvをエクスポートして、別のcsvファイルとして保存して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/2c60bbb4-07be-cdd9-1ffb-344ebb8d58bc.png)
出来上がったcsvファイルを確認すると、以下の様のfuzzyという列が新たに作成され、正しく文字が入っていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/fc8551bc-0fc8-ed38-8496-8a9daa4f5892.png)
なお、保存したファイルをそのまま読み込むだけでは、数字の部分が文字列として認識されてしまいます。そのため、ibaraki_pop_fuzzy.csvt　というファイルを作り、人口を数字として認識させます。その上で、fuzzyとN03_004をkeyにして結合し、人口別に表示すると、以下の通りになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39797/b85524e1-209f-133c-c607-b44f47c9869d.png)
処理した結果のファイルは、[こちらから](https://www.dropbox.com/s/phwuj7dhhidsx2t/ibaraki_fuzzy.zip?dl=0)ダウンロードできます。

## おわりに

ということで、こんな感じで文字列が完全に一致してない場合でも、QGIS上でテーブル結合ができました。ちょっとスクリプトをいじる必要があるのと、正規化をしたカラムをcsvファイルの方に入れる手順を取ったため、少し手間がかかりますが、別々の機関から入手したファイルや、エクセルでの表示のために空白等が含まれる場合など、データの間での表記揺れがあるファイルを結合させるには、便利に使える場合もあると思います。
また、正規化したカラムは、csvの方でなくgisのデータの方に作成することも可能なので、その場合はちょっとだけ、手間を省くことができます。
一方で、こうした表記揺れの修正等には、OpenRefine等のツールを使って、データを修正する方がいい場合もあると思います。以下の資料等も、参考にして下さい

 - [標本レコード修正のためのOpen refine（ド基礎編））](https://www2.slideshare.net/arosawa/open-refine-237216272)
 - [OpenRefineで神エクセルと戦う](https://qiita.com/higa4/items/5c2b2630bfd91e064f67)

[^1]: 正確な説明は、誰かお願い（汗