1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

「郵便番号から住所を引きたい...」
「電話番号から住所を持ってきたい...」

このへんをやろうとして、データを探し回った人はけっこう多いと思います。

郵便番号のデータはあります。住所のデータもあります。市外局番の一覧もあります。

でも、それらが紐づいたデータが無いんですよね。

ので今回あらゆる行政から公開されている "住所に関するデータをまとめたデータベース【jpac】" を作ってみました。

jpac

ここで公開中です!

データベース等のダウンロードはこちら👇

Parquet / SQLite / CSV.gz の3形式。中身は同じです。


こんなことができます

まず「何が引けるか」から。全部、1枚のテーブルか SQLite のビュー1本で引けます。

引きたいもの 引けるもの
郵便番号 その郵便番号が覆うすべての住所(1件とは限らない)
住所 郵便番号 / 国交省コード / 代表点の緯度経度
旧郵便番号(3桁・5桁) 現在の郵便番号 → 住所 → 国交省コード
市外局番 番号区画 → 市区町村 → その市区町村の郵便番号
国交省コード 住所 → 郵便番号
市区町村コード その市区町村の住所・郵便番号・市外局番

全部双方向です。郵便番号から住所も、住所から郵便番号も引けます。

たとえばこう。

import polars as pl

x = pl.read_parquet("jp_address_crosswalk.parquet")

# 郵便番号 → 住所(8行返る)
x.filter(pl.col("postal_code") == "1600023")

# 住所 → 郵便番号と座標
x.filter(pl.col("town_name") == "西新宿一丁目").select(
    "postal_code", "mlit_code", "mlit_latitude", "mlit_longitude"
)

実際にできることの例をいくつか。

  • 住所録の郵便番号を一括で検算する — 登録済みの郵便番号と住所の組が、公式の対応と一致するか
  • 平成初期の名簿を現在の住所に寄せる — 旧郵便番号 160 は、いまの郵便番号60個に分かれています
  • 市外局番からエリアを絞る — 番号区画 → 市区町村 → 郵便番号と辿れます
  • 住所に座標を付ける — 国交省の代表点(町単位)が付いてきます
  • 名寄せの答え合わせをする — 自前のマッチング結果と突き合わせて、食い違いだけ拾う

そして全部の行に、その対応がどれくらい信用できるかが付いています。ここが一番の売りです。


日本の住所の難しさ

そもそも、なんでこんなものを作る必要があったのか。日本の住所、思っているより素直じゃないです。

1つの郵便番号に、住所が何件も紐づく

郵便番号 1600023(東京都新宿区西新宿)を引くと、8行返ります。

住所 郵便番号 国交省コード ID
東京都新宿区西新宿一丁目 1600023 131040023001 jpa1dy9jn5hy738r9wnb
東京都新宿区西新宿2丁目 1600023 131040023002 jpa1d3c2z1tmnb0bnkcr
東京都新宿区西新宿3丁目 1600023 131040023003 jpa11p02cf9zmvg3f57r
…(四〜七丁目も同じ)
東京都新宿区西新宿8丁目 1600023 131040023008 jpa1tgdzg4wsmchae4e0

1600023 は一丁目から八丁目までをまとめて表す番号なので、8件とも正解です。

いちばん極端なのは山口県長門市の 7594401614件の住所を覆っています。バグではありません。
小字が細かく分かれている地域に、郵便番号が広めに割り当てられているだけです。

市外局番は、市区町村の境界を平気でまたぐ

栃木県小山市には市外局番が4つあります。

市区町村 市外局番
栃木県小山市 0285028002820296

3つ以上にまたがる市区町村は17あります。京都市、西宮市、新潟市、高山市、萩市、東広島市…。

「市区町村が決まれば市外局番も決まる」は成り立ちません。

旧郵便番号は、現在の番号に分裂している

1998年に3桁・5桁から7桁になったとき、1つの旧番号は複数の新番号に分かれました。

160(東京都新宿区)は、いまの60個の郵便番号に散っています。

旧郵便番号 現在の郵便番号 住所
160 1600001 東京都新宿区片町
160 1600005 東京都新宿区愛住町
160 1600016 東京都新宿区信濃町
…(全部で60個)

同じ町名が、全国にいくつもある

「西新宿」は東京都新宿区だけじゃありません。埼玉県蓮田市にも兵庫県佐用町にもあります。

町名で名寄せすると、普通に混ざります。

表記も揃っていない

同じデジタル庁のデータの中で「西新宿一丁目」と「西新宿2丁目」が混在しています。漢数字と算用数字、
全角と半角。

省庁をまたぐともっと増えます。ある省庁は「髙」、別の省庁は「高」で同じ町を書いています。

そもそも住所じゃない行が混ざっている

日本郵便のファイルには、住所ではないレコードが入っています。

種別 件数
通常の町字 122,603
「以下に掲載がない場合」 1,870
「〜一円」 23
「〜の次に番地がくる場合」 17

「以下に掲載がない場合」は特定の町を指していません。その市区町村のうち個別の郵便番号が振られて
いない部分を、まとめて表す行です。町として扱うと対応が壊れます。

つまり LIMIT 1 が危ない

こういうデータに対して LIMIT 1 を書くと何が起きるか。8件から適当に1件が選ばれます。

ORDER BY がなければ選ばれるのは実行計画次第なので、DBのバージョンを上げただけで結果が変わります。
こわい。

なので、このデータは選びません。8件返します。

1件に絞りたいときは、順番ではなく根拠で絞ってください。

# 自動で確定した対応だけ。曖昧なところでは何も返らない
x.filter(pl.col("postal_status") == "auto")

答えが1つに決まらないときは、何も返さないのが正直です。


なぜ無いのか

日本の「住所」、実は1つの役所が管理していません。

誰が 何を
デジタル庁 住所そのもの(町字)
日本郵便 郵便番号(現行7桁・旧3/5桁)
国土交通省 大字・町丁目コードと代表点の緯度経度
総務省 市外局番と番号区画

4つの役所が、別々の目的で、別々に運用しています。

そしてそれぞれ自分の担当分についてはめちゃくちゃ正確です。ちゃんと公開もしています。

ただ、互いの対応表は誰も出していません。

つまり「郵便番号 → 住所」という誰もが当たり前にやっている処理には、公式の正解が存在しません。

世の中の住所データはたいてい、どこかの誰かが名前で突き合わせた結果です。しかも、突き合わせた
痕跡は消えています。

じゃあ一度ちゃんと作るか、と思って作りました。痕跡は消さずに。


使い方

読み込む

import polars as pl

x = pl.read_parquet("jp_address_crosswalk.parquet")   # 727,110行 × 43列

pandasでCSVを読むときは dtype=str を忘れずに。付けないと 0640941640941 になります。

import pandas as pd

x = pd.read_csv("jp_address_crosswalk.csv.gz", dtype=str)

郵便番号 → 住所

x.filter(pl.col("postal_code") == "1600023").select(
    "pref_name", "city_name", "town_name", "machiaza_id", "mlit_code",
    "postal_confidence", "postal_status",
)

SQLite ならビュー1本です。

SELECT pref_name, city_name, town_name, machiaza_id, mlit_code,
       postal_relation_type, postal_confidence, postal_status
FROM   address_crosswalk
WHERE  postal_code = '1600023';

sqlite3 jp_address_crosswalk.sqlite で開けます。そのまま実行できるSQLも12本同梱しました。

1行が何を表しているか

1行 = 住所 × 郵便番号 × 国交省レコード の組み合わせです。

住所726,170件に対して727,110行あるのは、対応が複数ある住所がその分だけ展開されているからです。

なので一意キーはありません。件数を数えるときは行数じゃなくて異なり数で。

x.height                      # 727,110(行数)
x["address_id"].n_unique()    # 726,170(住所の件数)

何が入ってるか

28テーブル。中心に住所があって、そこから各制度へブリッジ(対応表)が伸びる形です。

データ全体像

件数
住所(町字) 726,170
郵便番号(現行7桁) 120,682
日本郵便のレコード 124,513(旧3/5桁を含む)
国交省 大字・町丁目 191,106(緯度経度つき)
番号区画 582
市内局番の割り当て 41,987
市区町村 1,918

対応がついた率はこんな感じ。

繋がり先 到達率
郵便番号 93.0% 675,161 / 726,170
国交省コード 25.3% 欠損ではなく粒度の違い。国交省側から見れば 96%
市外局番 99.7% 市区町村単位で 1,912 / 1,918。未達は北方領土の6件

繋がらなかったレコードも消していません。 住所542,792件、郵便レコード25,647件、国交省7,774件が
unresolved として残っています。

対応率は、行を消せばいくらでも上がってしまうので。

全行が「根拠」を持っている

43列のうち24列が根拠の列です。postal_ / mlit_ / telephone_ の3系統が同じ8項目を持ちます。

意味
*_relation_type 対応の付き方(exact / equivalent / ambiguous / unresolved …)
*_rule どの規則が生んだか(P1P7 / M1M5 / T1T10
*_confidence 確からしさ 0〜1
*_candidate_count 候補が何件あったか
*_is_unique 1件に定まったか
*_status auto / review_required / NULL

行を見れば、それがどれくらい信用できるか分かります。


「電話番号から住所」は、作れませんでした

冒頭の動機の半分です。結論から言うと無理でした。

市外局番から市区町村までは辿れます。でも「この町はこの市外局番」という町単位の対応を、
総務省は公表していません。

公表されているのは「この番号区画はこの市区町村を含む」まで。しかも
夕張市(富野を除く。) みたいな除外規定つきの日本語です。

埋めようと思えば埋められました。市区町村が分かれば、中の町に同じ局番を配ればそれっぽくなります。

でもそれ、公式が言ってないことを言ったことになるんですよね。

なので埋めませんでした。町ごとの市外局番の列は全行 NULL です。市区町村までなら答えます。
それ以上は何も答えません。

「できない」と分かったこと自体が収穫だと思うことにしました。


技術的なこと

machiaza_id を主キーにしてはいけない

デジタル庁は町字IDにも市区町村コードにも訂正を出します

合併すれば町が存続していても市区町村コードが変わるし、政令指定都市になれば区が入って振り直しです。

役所のコードをそのまま主キーにすると、そのたびに利用者側の保存データが黙って壊れます。

なのでIDは自分で発番しています。

address_id の採番と台帳

key    = "abr:town:" + lg_code + ":" + machiaza_id   # abr:town:131041:0023001
digest = BLAKE2s(key, 10バイト)
id     = "jpa1" + digest を Crockford base32 で16文字   # jpa1dy9jn5hy738r9wnb

ハッシュなので決定的です。ゼロから同じデータでビルドし直せば同じIDが出ます。

そして不透明です。IDから市区町村を読み取れません。読み取れると合併後にその読みが間違いに
なるので、わざとです。

Crockford base32(ILOU を除く)なので、読み違えや書き写しの事故も起きにくいです。

同じIDを引き継ぐ条件

2回目以降のビルドでは、台帳(726,170行)と今回のデータを突き合わせます。

規則 条件 結果
I1 キーが完全一致 同じIDを継続
I2 同一市区町村内でコードが訂正された。町名一致 + 旧キーが消滅 + 候補ちょうど1件 引き継ぐ
I3 市区町村コードが変わった。人手で登録した合併の事実あり + 候補ちょうど1件 引き継ぐ
I5 どれでもない、または候補が2件以上 新しいIDを発番
I6 退役済みのキーと一致 自動では復活させない

「候補ちょうど1件」にこだわっているのは、同名の町が全国にあるからです。

さっきの「西新宿」がまさにそれで、候補が複数あるときに1つ選ぶと、別の町のIDを継承させることに
なります。

なので絞れないときは新しいIDを振ります。誤爆するよりIDが1個増える方がマシです。

I3 が人手登録を要求するのも同じ理由で、「A市がB市に合併した」は市区町村の話であって、
「A市の○○町とB市の○○町が同じ町だ」の証拠にはならないからです。

confidence は確率ではない

全行に confidence が付いていますが、確率ではありません。

0.99 は「99%の確率で正しい」ではなく「発行元が公式に出したコード対応である」の意味です。

根拠
1.00 コードで明示 + 別の項目も裏づけ
0.99 公式のコード対応
0.97 名前が一致し、双方向で1つに定まった
0.70 市区町村までしか書かれていない
0.50 以下 候補どまり

要は規則IDを数値にしただけなので、平均を取っても掛けても意味がありません。

ちなみに「自動確定」の条件はSQLiteのDDL側でも縛っています。

CHECK (verification_status <> 'auto' OR (
    candidate_count = 1
    AND is_unique_match = 1
    AND candidate_count_is_complete = 1
    AND confidence >= 0.98
    AND override_stale = 0
    AND relation_type IN ('exact','equivalent')))

照合コードにバグが入っても、条件を満たさない行が auto で出ることはありません。

表記をむやみに揃えない

正規化はします。全角英数の半角化とか、丁目の漢数字→算用数字とか。

でもこのへんはわざと統一していません。

  • ヶ / ケ / が
  • ノ / 之 / の
  • 旧字体と新字体、高 / 髙、崎 /﨑
  • カタカナとひらがな

日本の地名、これらの違いが意味を持つことがあるので。

実際、統一していたら消えていた省庁間の実在する表記差が見つかりました。潰すべきノイズじゃなくて、
記録すべき事実です。

Word 97 との戦い

総務省の市外局番一覧、Word 97 の .doc でしか公開されていません。2026年に。(あとはPDF)

なので olefile でOLE2複合ドキュメントを開いて、WordDocument ストリームのFIBからピース
テーブルを辿って本文を復元する処理を60行ほど書きました。

電気通信番号指定状況の方は BIFF8 の .xls(マジックバイト D0CF11E0A1B11AE1 を確認)なので
xlrdopenpyxl はOOXMLしか読めません。

疑わしければ止まる

ビルドの状態遷移

スキーマ変更・ライセンス文言の変更・行数異常・重複異常・曖昧率の急増・説明のつかない住所の消滅・
SHA-256不一致 ── 1つでもあればリリースを作りません。

規約の本文も毎回ハッシュを取って、レビュー済みのベースラインと照合しています。規約が変わったら
ビルドが止まります。

テストじゃなくて「検証」

単体テストとは別に、出来上がったデータを元ファイルと突き合わせるツールを用意しています。

jpac verify              # 下記を全部
jpac verify sources      # 全入力行がDBに届いたか(行落ち検出)
jpac verify fields       # 全行・全列を元ファイルと突き合わせ
jpac verify cross-source # 出典同士の食い違い、座標の地理的妥当性
jpac verify artifacts    # 3ファイルが同じ答えを返すか、文字化けが無いか
jpac verify distribution # 分布の妥当性、フラット表の再計算一致

fields26,360,566個のフィールドを比較します。全行・全列、サンプリングなし。

で、これまでに見つかった行落ちバグ、全部このツールで見つかりました。 単体テストでは1件も
見つかっていません。

「テストが通る」と「データが正しい」は別の話でした。


正直に書いておく

  • 合併でIDは引き継がれません。 合併の事実を人手で登録する必要があって、V1では空のまま出して
    います。合併した町は古いIDが退役して新しいIDが振られます。
  • 緯度経度は代表点です。建物の位置ではないです。
  • 番地・号は扱いません。 「西新宿一丁目」までです。
  • バイト単位の再現性はありません。 IDも並び順も決まっているので論理的には同一ですが、
    observed_from などが時計から入るので、2回のビルドはバイト一致しません。

おわりに

「郵便番号から住所を引きたい」だけのつもりが、気づいたら28テーブルになっていました。

分からないものは分からないまま返します。8件あるなら8件返すし、公表されていない列は空のままです。
その代わり、なぜそう言えるのかを全行が持ち歩いています。

「電話番号から住所」は結局できませんでしたが、なぜできないのかは説明できる形になりました。
次に同じことをやろうとした人が、同じ場所で時間を溶かさずに済むはずです。

Issue や PR もお待ちしています。特に「この対応おかしくない?」は大歓迎です。根拠の列があるので、
どの規則が間違えたのかまで特定できます。

コードは MIT ですが、データは MIT ではありません。 デジタル庁・国交省・総務省のデータは
公共データ利用規約(第1.0版)で、出典表示に加えて加工した旨の明示が必要です。日本郵便は
著作権を主張していません。

実際に使ったファイルから生成した NOTICE.md を同梱しているので、再配布のときはそれをそのまま
添えてください。


改訂履歴

更新日 更新内容
2026年9月1日 初版を公開しました。

本記事は、公開情報の変更があった場合、随時更新します。
リンク切れ、提供終了、ライセンス変更などを見つけた場合は、コメントでお知らせいただけると助かります。


過去記事は以下のリンクから!
キャラクターイメージ_Qiitaカード_v1.png

X / GitHubで色々公開してます!
キャラクターイメージ_Xカード_v1.png
キャラクターイメージ_githubカード_v1.png

記事中の数値はすべて v1.0.0+data-2026-08-23 の実測値です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?