[pukiwiki]
Pythonで名寄せするプログラムを書いてみました。
(まだ年賀状のリスト作りには早いですけれども。)
参考文献
-[[【PDF】 圧縮を用いた類似度判定のための計算実験:http://www.tani.cs.chs.nihon-u.ac.jp/g-2008/shu/tyukan_shu.pdf]]
文字列類似度の判定には、こちらの式を、ほぼ そのまま使用しています。
[/pukiwiki]
[pukiwiki]
zlib(gzip)による圧縮を利用しているので、たぶん [[Nグラム>ググる:Nグラム]]による比較に近い結果が得られるんじゃないかという期待から、上記のアルゴリズムを使用しました。
—-
具体的には、表記にブレのある住所録ファイルsample.csvについて、似た文字列同士が隣り合わせになるように並べなおします。
(むしろ、クラスタリングに近いかも)
動作確認 CPython2.5 vista
IronPythonでは動かないみたいです。
(文字列.encodeメソッドでコケるのですが、どう直したらよいやらわからず)
動作スピード 一例 。 Core2Duo T7250 2GHzにて
手持ちの住所録等にて実験。
-35件 ー> 0.64秒
-1000件 -> 169秒 = 2分49秒
件数の1.5乗のオーダーで処理時間が増えている、かな?
市町村規模の名簿の名寄せはムリっぽいですが、1000件ぐらいなら 実用に使える速度になった模様。(自画自賛)
//35件のほうについては、こちらのサンプルデータを使用しました
//-[[データバンク Repoラボ:http://lab.irepo.jp/identification/service.php]]
//sample.csvとして保存
*今後の課題
今回は単純に一軒ごとに文字列データとして比較しました。
使用したサンプルのようにcsvデータの場合、AdrSortクラスの scoreメソッドにて、カンマで分解、項目ごとに細かいスコアリングをすると精度が上がるかも。
(たとえば、電話番号が一致してたら、他が全部違っても名寄せする、とか)
また、[[GIS>ググる:GIS]]を利用する、というのも一つの方法かも。
ただし、住所をやたらとAPIに投げるのは厭っぽいですが。。。
[/pukiwiki]
—-
以下コード
adrsort.py
カレントディレクトリの sample.csvを似ている順に並べなおし、sorted.csvとして保存
# -*- coding: utf-8 -*-
import csv
import codecs
import difflib
import unicodedata
import re
from mycollections import namedlist
Address=namedlist("Address","name post tel address")
Dat=namedlist("Dat","idx ln n z a")
def minus(x):
ret=[]
g=x.groups()
if g==[None,None,None,None] :return
for i in g:
if not i :continue
i=re.match("\d+",i).group()
ret.append(i)
return "-".join(ret)+" "
def rep(t,
pat=re.compile(r"(?P<a>\d+\s?" u"丁目" r")?" r"(?P<b>\d+\s?" u"番地" r")?"\
r"(?P<c>\d+\s?" u"号" r")?" r"(?P<d>\d+\s?)?")
):
t=pat.sub(minus,t)
return t
#表記のブレを正規化
def normal_adr(txt):
return rep(normal(txt))
def normal(txt):
txt=txt.replace(u" ","")
return unicodedata.normalize("NFKC",txt)
def normal_tel(txt):
t=normal(txt)
t=t.replace("-","")
return t
class ScoreZ(object):
def __init__(self):
self.z_dict={}
def score(self,t1,t2):
#完全一致のときゼロを返す
t1,t2=sorted( [t1,t2])
t12=t1+t2
tlist=[t1,t2,t12]
for t in tlist:
if not t in self.z_dict:
self.z_dict[t]=len(t.encode("zlib"))
sz1,sz2,sz12=[ self.z_dict[t] for t in tlist]
szmin,szmax=sorted([sz1,sz2])
#return float(max(len(t1),len(t2)))*(sz12-szmin)/szmax
return 100*(sz12-szmin)/szmax
class AdrSort(object):
nlist=[normal,normal_tel,normal_adr,normal_tel,normal]
def __init__(self,datlist,threashold=45):
self.datlist=datlist
self.donelist=[]
self.yetlist=range(len(datlist))
self.scorez=ScoreZ()
self.threashold=threashold
def score(self,txt1,txt2):
#txt1, tx2がcsvの場合、カンマで区切って、それぞれ
#適宜 スコアリングの係数を変えたり、正規化の手法を変えると
#精度が上がるかも。
scorez=self.scorez
txt1=normal_adr(txt1)
txt2=normal_adr(txt2)
return scorez.score(txt1,txt2)
def search(self,idx):
datlist=self.datlist
yetlist=self.yetlist
donelist=self.donelist
t1=datlist[idx]
scorez=self.scorez
sclist=[]
threashold=self.threashold
for y in yetlist :
t2=datlist[y]
sc=self.score(t1,t2)
sclist.append((sc,y))
return [ y for sc,y in sorted(sclist) if sc <threashold]
def sort(self):
datlist=self.datlist
yetlist=self.yetlist
donelist=self.donelist
scorez=self.scorez
sclist=[]
threashold=self.threashold
while yetlist:
idx1=yetlist.pop(0)
print idx1,
donelist.append(idx1)
candies=self.search(idx1)
for c in candies:
donelist.append(c)
yetlist.pop(yetlist.index(c))
for c in candies:
candies2=self.search(c)
for c2 in candies2:
donelist.append(c2)
yetlist.pop(yetlist.index(c2))
def load():
#fp=codecs.open("sample.csv",encoding="cp932")
fp=codecs.open("sample.csv",encoding="utf8")
lines=fp.read().splitlines()
fp.close()
lines=list(set(lines))
lines.sort()
return lines
def main():
datlist=load()
print len(datlist)
a=AdrSort(datlist)
import time
print time.localtime()
t0=time.time()
a.sort()
print time.localtime()
print time.time()-t0
ret=[ datlist[i] for i in a.donelist]
ret.append("")
fp=open("sorted.csv","wb")
fp.write("\n".join(ret))
fp.close()
if __name__=="__main__":
main()
カスタマイズ例 scoreメソッドを差し替え
class AdrSort2(AdrSort):
nlist=[normal,normal_tel,normal_adr,normal_tel,normal]
def score(self,txt1,txt2):
scorez=self.scorez
u"例)氏名,〒,住所1,住所2,TEL,備考"
name1,post1,adr1a,adr1b,tel1,memo1=txt1.split(",")
name2,post2,adr2a,adr2b,tel2,memo2=txt2.split(",")
adr1=adr1a+adr1b
adr2=adr2a+adr2b
dlist1=[name1,post1,adr1,tel1,memo1]
dlist2=[name2,post2,adr2,tel2,memo2]
sc=[]
for i in xrange(5):
d1=dlist1[i]
d2=dlist2[i]
n=self.nlist[i]
dlist1[i]=n(d1)
dlist2[i]=n(d2)
s=scorez.score("".join(dlist1),"".join(dlist2))
return s