にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

郵便番号を何桁読めば都道府県が判定できるのか?

はじめに

郵便番号から都道府県を取得したい。理由は、宅配便の配送料*1を計算するのに使いたかったので。


郵便番号と自治体等の住所の対応が調べられるデータを日本郵便が配布している。

これを使って郵便番号→都道府県を辞書引きすれば、郵便番号から都道府県を調べることができるはずである。
しかし、このデータは約12.5万行ある。47の都道府県を調べるために12.5万行のルックアップテーブルを持つのはちょっと大げさな気がする。もし郵便番号を7桁見なくても都道府県を判定できるのなら、それがいいのでは?

そう思って、郵便番号を何桁見れば都道府県を特定できるのかを調べてみた。

結論

結論を書くと、郵便番号から都道府県を完全に特定することはできない。理由は、同一の郵便番号が府県が異なる2つの市町村に紐づけられている例が3つあるからである。

郵便番号 住所
4980000 三重県桑名郡木曽岬町(掲載がない場合)
愛知県弥富市(掲載がない場合)
6180000 京都府乙訓郡大山崎町(掲載がない場合)
大阪府三島郡島本町(掲載がない場合)
8710000 大分県中津市(掲載がない場合)
福岡県築上郡吉富町(掲載がない場合)
町域の「掲載がない場合」というのは、「その他」に近い意味で、他の町域には郵便番号が割り当てられているが、それに該当しないところである、ということを指すようだ。数は少ないだろうとは思うが、正確性が必要な場合は無視できない。

他にも複数の市町村に同一の郵便番号が割り当てられている例があるが、都道府県が異なるのはこの3つのみである。

府県が異なる2つの市町村に結び付いた郵便番号を除いたとしても、7桁目だけが異なって都道府県が変わる例が多数あるため、結局7桁全部みないと都道府県判定はできない。

とはいえ

郵便番号の最初の2桁目までで大体の都道府県が特定できるらしい。近い番号と都道府県が異なっているものを例外として除外すると傾向が見えてくる。

郵便番号データは2020年11月30日更新のものを利用した。

00: 北海道
01: 秋田県
	「018550 ⻘森県」を除く
02: 岩手県
03: 青森県
04-09: 北海道
10-20: 東京都
21-25: 神奈川県
26-29: 千葉県
30-31: 茨城県
	「311441 栃⽊県」を除く
32: 栃木県
33-36: 埼玉県
	「349122 栃⽊県」を除く
37: 群馬県
	「3701507 埼⽟県」を除く
38-39: 長野県
	「3840097 群⾺県」を除く
	「389012 群⾺県」を除く
	「389226 新潟県」を除く
40: 山梨県
41-43: 静岡県
	「431412 愛知県」を除く
44-49: 愛知県
	「4980000 三重県または愛知県」を除く
	「49808 三重県」を除く
50: 岐阜県
51: 三重県
52: 滋賀県
	「520046 京都府」を除く
53-59: 大阪府
	「56308 兵庫県」を除く
60-62: 京都府
	「6180000 京都府または⼤阪府」を除く
	「6180001〜6180004 ⼤阪府」を除く
	「618001〜618002 ⼤阪府」を除く
63: 奈良県
	「630027 ⼤阪府」を除く
64: 和歌山県
	「647127 奈良県」を除く
	「64713 三重県」を除く
	「64715 奈良県」を除く
	「64803 奈良県」を除く
65-67: 兵庫県
68: 鳥取県
	「68401〜68404 島根県」を除く
	「685 島根県」を除く
69: 島根県
70-71: 岡山県
72-73: 広島県
74-75: 山口県
76: 香川県
77: 徳島県
78: 高知県
79: 愛媛県
80-83: 福岡県
	「8115 ⻑崎県」を除く
	「817 ⻑崎県」を除く
	「839142 大分県」を除く
84: 佐賀県
	「84804 長崎県」を除く
85: 長崎県
86: 熊本県
87: 大分県
	「8710000 ⼤分県または福岡県」を除く
	「871022 福岡県」を除く
	「87108〜87109 福岡県」を除く
88: 宮崎県
89: 鹿児島県
90: 沖縄県
91: 福井県
92: 石川県
	「9220679 福井県」を除く
93: 富山県
	「939017 ⽯川県」を除く
94-95: 新潟県
	「949832 ⻑野県」を除く
96-97: 福島県
98: 宮城県
99: 山形県

都道府県が異なる例外として除外した番号を調べると、郵便番号の最初の2桁の示す都道府県に隣り合う市町村であることがわかる(全て調べたわけではないので例外はあるかもしれない)。配送上の都合だったりするのだろうか?

という訳で、配送料には同一都道府県内の料金が設定されているため、都道府県が特定できないと送料を決定することができない。そのため、送料の判定に郵便番号を使うことは基本的にできない*2。目論見が外れた結果となった。残念。

コード

使ったPython 3コードを置いておく。日本郵便からダウンロードした全国一括の郵便番号データKEN_ALL.CSVと同じディレクトリに入れて使う。

ここまで読めば都道府県が一意に定まるよ、というのを調べるためのもの

#coding: utf-8
import csv, codecs

CSVFILENAME = "KEN_ALL.CSV"

#0 全国地方公共団体コード(JIS X0401、X0402)……… 半角数字
#1 (旧)郵便番号(5桁)……………………………………… 半角数字
#2 郵便番号(7桁)……………………………………… 半角数字
#3 都道府県名 ………… 半角カタカナ(コード順に掲載) (注1)
#4 市区町村名 ………… 半角カタカナ(コード順に掲載) (注1)
#5 町域名 ……………… 半角カタカナ(五十音順に掲載) (注1)
#6 都道府県名 ………… 漢字(コード順に掲載) (注1,2)
#7 市区町村名 ………… 漢字(コード順に掲載) (注1,2)
#8 町域名 ……………… 漢字(五十音順に掲載) (注1,2)
#9 一町域が二以上の郵便番号で表される場合の表示 (注3) (「1」は該当、「0」は該当せず)
#10 小字毎に番地が起番されている町域の表示 (注4) (「1」は該当、「0」は該当せず)
#11 丁目を有する町域の場合の表示 (「1」は該当、「0」は該当せず)
#12 一つの郵便番号で二以上の町域を表す場合の表示 (注5) (「1」は該当、「0」は該当せず)
#13 更新の表示(注6)(「0」は変更なし、「1」は変更あり、「2」廃止(廃止データのみ使用))
#14 変更理由 (「0」は変更なし、「1」市政・区政・町政・分区・政令指定都市施行、「2」住居表示の実施、「3」区画整理、「4」郵便区調整等、「5」訂正、「6」廃止(廃止データのみ使用))

postal2pref = dict()
with codecs.open(CSVFILENAME, 'r', encoding="shift_jis") as csvfile:
    reader = csv.reader(csvfile)
    for row in reader:
        postalcode = row[2]
        prefecture = row[6]
        if postalcode in postal2pref:
            postal2pref[postalcode].add(prefecture)
        else:
            postal2pref[postalcode] = set([prefecture])

# headで始まる郵便番号の示す都道府県の種類の数を数える
def check_pref_startswith_rec(head):
    if len(head) > 7: return #8文字以上の郵便番号はありえない
    
    keys_startswith_head = [key for key in postal2pref.keys() if key.startswith(head)]
    values_startswith_head = set(map(lambda k: ", ".join(postal2pref.get(k)), keys_startswith_head))
    #print(len(values_startswith_head))
    if len(values_startswith_head) == 0: #郵便番号が存在しない場合
        return
    elif len(values_startswith_head) == 1: #都道府県が一つに定まった場合
        print("{}: {}".format(head, "".join(values_startswith_head))) #出力
        return
    else: #都道府県が一つに定まらない
        for n in range(0, 10):
            check_pref_startswith_rec(head + str(n))
            
if __name__ == '__main__':
    check_pref_startswith_rec('')

例外の番号を除外して同様のことをするもの

#coding: utf-8
import csv, codecs

CSVFILENAME = "KEN_ALL.CSV"

#2 郵便番号(7桁)……………………………………… 半角数字
#6 都道府県名 ………… 漢字(コード順に掲載) (注1,2)

startswith_exclude_list = ["018550", "311441", "349122", "3701507", "3840097", "389012", "389226",
                           "431412", "4980000", "49808", "520046", "56308", "6180000", 
                           "6180001", "6180002", "6180003", "6180004", "618001", "618002",
                           "630027", "647127", "64713", "64715", "64803", "68401", "68402", "68403", 
                           "68404", "685", "8115", "817", "839142", "84804", "8710000", "871022", 
                           "87108", "87109", "9220679", "939017", "949832"]
def check_exclude(postalcode):
    excludeflag = list(map(postalcode.startswith, startswith_exclude_list)).count(True)
    if excludeflag > 0:
        return True
    else:
        return False

postal2pref = dict()
with codecs.open(CSVFILENAME, 'r', encoding="shift_jis") as csvfile:
    reader = csv.reader(csvfile)
    for row in reader:
        postalcode = row[2]
        prefecture = row[6]
        if check_exclude(postalcode):
            continue
        if postalcode in postal2pref:
            postal2pref[postalcode].add(prefecture)
        else:
            postal2pref[postalcode] = set([prefecture])

# headで始まる郵便番号の示す都道府県の種類の数を数える
def check_pref_startswith_rec(head):
    if len(head) > 7: return #8文字以上の郵便番号はありえない
    
    keys_startswith_head = [key for key in postal2pref.keys() if key.startswith(head)]
    values_startswith_head = set(map(lambda k: ", ".join(postal2pref.get(k)), keys_startswith_head))
    #print(len(values_startswith_head))
    if len(values_startswith_head) == 0: #郵便番号が存在しない場合
        return
    elif len(values_startswith_head) == 1: #都道府県が一つに定まった場合
        print("{}: {}".format(head, "".join(values_startswith_head))) #出力
        return
    else: #都道府県が一つに定まらない
        for n in range(0, 10):
            check_pref_startswith_rec(head + str(n))
            
if __name__ == '__main__':
    check_pref_startswith_rec('')

*1:ヤマト、ゆうパックは離島料金がかからないので都道府県が分かれば配送料が計算できる。佐川は離島は別料金。

*2:東京は郵便番号上2桁のみで完全に特定できるので、東京発の荷物の送料は送り先郵便番号上2桁のみで決定できる。北海道等も同様。