読者です 読者をやめる 読者になる 読者になる

超簡易的な住所文字列の処理系を作った

Python 作業メモ

住所文字列の表記揺れを目視で確認する作業が発生して、衝動に任せて作った代物です。コードを読んでいただけばわかりますけど抜けだらけです(ジオコーダ作るときの指標ぐらいにはなるかも)。

このコードは住所文字列のCSVを読み込んで、都道府県/市区町村/大字/町丁目/それ以外 の列として切り出します。必要なものは市区町村、大字、町丁目の辞書データです(これは./address_base/以下に格納)。また、表記揺れを解消するための辞書を読み込む機能を作っています。これは作業フォルダ以下(./WordDictionary/WordDictionary.csv)においてあり、修正前文字列/修正後文字列をCSVの形で持っています(「粕屋郡,糟屋郡」みたいなCSVです)。

出力はExcel形式で、これはPandas使って吐きだししています。最近Pandasはフォーマット変換のために多用しているなあ。

import csv
import pandas as pd

AddressList = []

with open('./ImportAddress.csv') as f:
    reader = csv.reader(f)
    for row in reader:
        AddressList.append(row[0])

def AddressDictionary(AddressCSV):
    TempList = []
    with open(AddressCSV, 'r') as f:
        reader = csv.reader(f)
        for row in reader:
            TempList.append(row[0])
    TempList.sort(key=len)
    TempList.reverse()
    return TempList

def DiffWritenWord(WordDictionary):
    TempList = []
    with open(WordDictionary) as f:
        reader = csv.reader(f)
        for row in reader:
            TempList.append(row)
    return TempList

DiffCSV  = './WordDictionary/WordDictionary.csv'

DiffWordList = DiffWritenWord(DiffCSV)
        
CityCSV  = './address_base/city_name.csv'
OazaCSV  = './address_base/oaza_name.csv'
ChomeCSV = './address_base/chome_name.csv'

CityList  = AddressDictionary(CityCSV)
OazaList  = AddressDictionary(OazaCSV)
ChomeList = AddressDictionary(ChomeCSV)

NormalizedAddressList = []

for basement in AddressList:
    WorkingList = []
    WorkingList.append(basement)
    
    for diffword in DiffWordList:
        if diffword[0] in basement:
            basement = basement.replace(diffword[0], diffword[1])
    
    if '東京都' in basement:
        WorkingList.append('東京都')
        basement = basement.replace('東京都', '')
    else:
        WorkingList.append('東京都')

    for cityname in CityList:
        if cityname in basement:
            WorkingList.append(cityname)
            basement = basement.replace(cityname, '')
            break
    if len(WorkingList) == 2:
        WorkingList.append('NaN')

    for oazaname in OazaList:
        if oazaname in basement:
            WorkingList.append(oazaname)
            basement = basement.replace(oazaname, '')
            break
    if len(WorkingList) == 3:
        WorkingList.append('NaN')

    for chomename in ChomeList:
        if chomename in basement:
            WorkingList.append(chomename)
            basement = basement.replace(chomename, '')
            break
    if len(WorkingList) == 4:
        WorkingList.append('NaN')

    WorkingList.append(basement)
    NormalizedAddressList.append(WorkingList)

Df = pd.DataFrame(NormalizedAddressList)
Df.to_excel('./Exported_Excel.xlsx')

コードを読むと抜けている部分が見えてきます。このスクリプトは住所の親子関係を完全に無視していますので、「福岡市東区守恒」みたいな住所も処理してしまいます(守恒は北九州市の競馬場のとこらへん)。 今回の処理では前述のようなふざけた住所はなかったものの、世の中には大字「上」とか「下」とかありますので、この処理系ではすぐに破綻します。

表記揺れの対処のために書いたスクリプトなんでこれで問題ないんですが、ジオコーダとか作りたい場合にはもうちょっと考えて作らないといけません。

近い将来自分の手でジオコーダ作ろうと思っているので、気が付いた点を備忘録的に書いておきます。

ジオコーダのための個人用Tips

  • 辞書はデータベースに格納する
  • 辞書データは住所レベルで区切っておく(都道府県/市区町村/大字/小字/町丁目とか)
  • psycopg2とか使ってDBに連携
  • WHERE句で親住所(東京都)を抽出しその子要素をメモリ上に展開する
  • 対象住所文字列を昇順/降順に並べ替えて最適化しておく
  • 子要素の検索では基本的に前方一致で処理を回す
  • 前方一致で三文字目くらいまでに該当文字が出てくれば許す(大字云々とかあるから)
  • 可能であれば最後まで残った住所(数字列)も辞書に当てていく
  • 枝番まで検索して、全然近しい住所がなかったら地番の辞書に当てに行くと尚よい
  • 日本の住所体系は辛い

ジオコーダ作るためには住所の辞書データが必要です。 街区レベル位置参照情報と国土数値情報つかってどこまでできるかやってみようと思います。