e-Statの地域メッシュ統計で商圏人口を比べる — Claude Codeで年齢別に集計
出店候補地ごとに半径内の年齢層別人口をe-Statの地域メッシュ統計から集計し、比較表にする手順です。メッシュコードから座標を出す式と、秘匿値「*」で合計がずれる点まで書きます。
出店候補地を比べるとき、市区町村の人口では粗すぎます。同じ区の中でも、駅前と住宅街では半径1km内の人口がまるで違うからです。
e-Statの統計地理情報システムには、国勢調査の結果を約1km四方の区画に割り振った地域メッシュ統計があります。5歳階級別人口をダウンロードし、候補地の座標から半径内のメッシュを足し上げれば、年齢層別の商圏人口を並べた表が作れます。Claude Codeには、座標計算と集計のスクリプトを書かせて検算まで回させる使い方が向きます。
この記事は、データの選び方、メッシュコードから座標を出す式、集計スクリプト、そして結果をそのまま信じてはいけない箇所の順に進めます。統計表IDをAPIで探す手順はe-Stat APIをClaude Codeで呼ぶ手順にあり、ここでは扱いません。
商圏人口に使うのは5歳階級別人口の地域メッシュ統計
地域メッシュ統計は、国土を緯度・経度で方形に細分した区画に、統計調査の結果を対応させたものです。統計局は国勢調査と経済センサスについて作成しており、商圏の「人」を見るなら国勢調査側を使います。
区画の大きさは、基準地域メッシュ(第3次地域区画)が一辺約1km、分割したものが約500m・約250m・約125mです。
メッシュの大きさと使い分け
基準地域メッシュ
コードは8桁です。半径2〜3km以上の商圏を広く比べる用途に向きます。
2分の1・4分の1メッシュ
コードは9桁・10桁です。徒歩圏のように半径1km前後を見るなら、こちらのほうが円の輪郭に近づきます。
令和2年国勢調査の編成区画は、基準地域メッシュ、2分の1地域メッシュ、4分の1地域メッシュです。8分の1にあたる6次メッシュ(約125m)は、令和7年12月に参考表として提供が始まりました。基本単位区のデータを機械的に配分して作るため、統計局は精度を理解した上で参考として使うよう案内しています。商圏の比較には、まず4分の1以上の区画までで足ります。
年齢別の列は、e-Statの区分でいう「5歳階級別人口」の統計表に入っています。0〜4歳から90〜94歳までの5歳刻みの人口で、人口(総数)の列も同じ表に含まれます。0〜14歳、15〜64歳、65歳以上のようなまとまりの列は、別の「人口及び世帯」の表にあります。5歳刻みの列を足して年齢層を作る場合は、この分け方を前提にします。
どの調査年を選ぶか
メッシュ統計の更新情報に並ぶ国勢調査は、令和2年と平成27年です。e-Statの統計地理情報システムのお知らせには、令和7年国勢調査の人口等基本集計(都道府県及び市区町村)が2026年10月2日に提供開始とあります。市区町村単位の集計であり、メッシュ単位のデータではありません。
したがって、メッシュ単位で商圏を切るなら令和2年国勢調査が最新の材料になります。表にした人口には、令和2年国勢調査の常住地ベースだと注記しておくと、読む側が鮮度を判断できます。
ダウンロードから比較表までの流れ
商圏人口の比較表ができるまで
- 1
統計データダウンロードで調査年とメッシュ区画を選ぶ
統計地理情報システムの「統計データダウンロード」で、政府統計名、調査年、メッシュ区画の順に選びます。ここで定義書も開けるので、列名と各列の意味を控えます。
- 2
提供単位を決める
データは第1次地域区画単位と、令和7年10月に加わった都道府県単位があります。候補地が1つの県に収まるなら、都道府県単位のほうが余計な区画を含みません。
- 3
候補地の座標を用意する
緯度・経度を世界測地系の10進数で並べます。住所から座標を出す作業は別のツールに任せ、Claudeには座標だけを渡すと取り違えが起きにくくなります。
- 4
半径内のメッシュを足し上げる
各メッシュの中心と候補地の距離を計算し、半径に入る区画の年齢別人口を合計します。
- 5
秘匿値と測地系を点検して表にする
「*」の有無とJGD2000・JGD2011の別を確かめてから、候補地を列にした表にします。
CSVの列名は、調査年と区画ごとの定義書で決まります。メッシュコードの列名も、5歳階級の列の並びも同じです。Claudeに集計を書かせる前に、先頭数行を見せて列名を確定させます。
head -3 downloaded-mesh.csvメッシュコードから中心座標を出す式
メッシュコードは、第1次地域区画の南端緯度と西端経度、そこからの細分の番号を並べたものです。逆に言えば、コードから区画の位置を計算で戻せます。
基準地域メッシュ(8桁)の構成は次のとおりです。
| 桁 | 意味 | 区画の幅 |
|---|---|---|
| 1〜2桁 | 意味第1次地域区画の緯度。南端緯度×1.5の値 | 区画の幅緯度40分 |
| 3〜4桁 | 意味第1次地域区画の経度。西端経度の下2桁 | 区画の幅経度1度 |
| 5桁・6桁 | 意味第2次区画の南から・西からの番号(0〜7) | 区画の幅緯度5分、経度7分30秒 |
| 7桁・8桁 | 意味基準メッシュの南から・西からの番号(0〜9) | 区画の幅緯度30秒、経度45秒 |
上2桁に1.5を掛ける理由は、第1次区画が緯度40分ごとに区切られ、緯度1度が1.5区画に当たるからです。分割メッシュは、9桁目以降に南西・南東・北西・北東の順で1〜4を付けます。
この式で、南端の緯度と西端の経度は次のように戻せます。
- 緯度 = 上2桁÷1.5 + 5桁目×5分 + 7桁目×30秒
- 経度 = 3〜4桁目+100 + 6桁目×7分30秒 + 8桁目×45秒
区画の幅の半分を足せば中心になります。下の関数は、9桁・10桁のコードにも対応させたものです。上の式から組んだ例で、東京駅付近の基準メッシュ 53394611 は、緯度およそ35.679、経度およそ139.769の中心に戻ります。
緯度で区画の大きさが変わる
基準メッシュは一辺約1kmとされますが、正確には位置で変わります。統計局の資料は、札幌市の基準メッシュの横幅が1,018m、那覇市が1,249mで、面積は札幌が約94万3千㎡、那覇が約115万3千㎡と示しています。縦はどちらも約920m台です。
遠く離れた候補地どうしを比べるときは、メッシュの数ではなく面積で割った人口密度も並べると、この差を吸収できます。
半径内の年齢層別人口を集計するスクリプト
次のスクリプトは、候補地ごとに半径内のメッシュを拾い、年齢層別の人口を足します。列名は定義書の記載に置き換える前提の仮の値です。
import csv
import math
import sys
# 定義書で確認した列名に合わせて書き換える(下の値は仮)
MESH_COL = "メッシュコード列名"
TOTAL_COL = "人口(総数)の列名"
AGE_COLS = { # 年齢層 -> 5歳階級別人口の列名
"0-14歳": ["0〜4歳の列名", "5〜9歳の列名", "10〜14歳の列名"],
"15-64歳": [],
"65歳以上": [],
}
CANDIDATES = { # 候補地の名前 -> (緯度, 経度)。世界測地系の10進数
"A駅前": (35.6812, 139.7671),
"B住宅街": (35.7000, 139.7000),
}
RADIUS_M = 1000
def mesh_center(code: str):
"""基準(8桁)・2分の1(9桁)・4分の1(10桁)メッシュの中心を返す"""
p, u = int(code[0:2]), int(code[2:4])
q, v, r, w = int(code[4]), int(code[5]), int(code[6]), int(code[7])
lat = p / 1.5 + q * 5 / 60 + r * 30 / 3600
lon = u + 100 + v * 7.5 / 60 + w * 45 / 3600
h_lat, h_lon = 30 / 3600, 45 / 3600 # 基準メッシュの緯度・経度の幅
for d in code[8:]:
h_lat, h_lon = h_lat / 2, h_lon / 2
n = int(d) - 1 # 1:南西 2:南東 3:北西 4:北東
lat += (n // 2) * h_lat
lon += (n % 2) * h_lon
return lat + h_lat / 2, lon + h_lon / 2
def dist_m(a, b):
la1, lo1, la2, lo2 = map(math.radians, (*a, *b))
h = math.sin((la2 - la1) / 2) ** 2 + (
math.cos(la1) * math.cos(la2) * math.sin((lo2 - lo1) / 2) ** 2
)
return 2 * 6371008.8 * math.asin(math.sqrt(h))
def num(s):
s = s.strip()
return None if s in ("*", "") else int(s) # 「*」は秘匿
def main(path):
result = {n: {"総数": 0, "秘匿": 0, **{k: 0 for k in AGE_COLS}} for n in CANDIDATES}
with open(path, encoding="utf-8-sig", newline="") as f:
for row in csv.DictReader(f):
c = mesh_center(row[MESH_COL].strip())
for name, pos in CANDIDATES.items():
if dist_m(c, pos) > RADIUS_M:
continue
t = num(row[TOTAL_COL])
result[name]["総数"] += t or 0
hidden = False # 1行につき1回だけ数える
for age, cols in AGE_COLS.items():
vals = [num(row[col]) for col in cols]
hidden = hidden or any(v is None for v in vals)
result[name][age] += sum(v or 0 for v in vals)
if hidden:
result[name]["秘匿"] += 1
return result
if __name__ == "__main__":
for name, r in main(sys.argv[1]).items():
print(name, r)半径内の合計と「*」の件数は、小さな架空のCSVで想定どおりに出る形にしてあります。実データでは列名とエンコーディングが変わるため、先頭行の確認を先に済ませます。
「秘匿」の欄は、「」を含む行を何件足し上げたかの件数です。ただし差の大きさを示す数ではありません。秘匿された値は「」の行ではなく合算先のメッシュに足されるため、半径の内外どちらに合算先があるかで、年齢別の合計が総数より大きくなるか小さくなるかが変わります。件数は、差を調べる行の目安として使います。
半径と区画の関係
円の内側かどうかを区画の中心点で判定するので、円の縁にかかるメッシュは丸ごと入るか丸ごと外れるかのどちらかです。次の目安は編集上の判断であり、統計局の基準ではありません。
| 見たい半径 | 向く区画 | 理由 |
|---|---|---|
| 500m前後(徒歩数分) | 向く区画4分の1メッシュ(約250m) | 理由1kmメッシュでは1区画も入らない候補地が出る |
| 1km前後 | 向く区画2分の1または4分の1 | 理由縁の誤差が円の面積に対して小さくなる |
| 3km以上 | 向く区画基準メッシュ | 理由区画数が増え、縁の誤差が相対的に薄まる |
Claude Codeに任せる範囲を決めておく
集計そのものは決定的な計算なので、Claudeに値を「考えさせる」場面はありません。任せるのは、スクリプトの作成、列名の対応づけ、検算の3つです。取り違えを防ぐため、プロジェクトのCLAUDE.mdに次のような規約を置いておきます。
## 商圏集計の規約
- メッシュ統計の列名は、定義書の記載と CSV の先頭行を突き合わせてから使う。推測しない
- 人口は集計スクリプトの出力だけを使う。数字を文章で書き直すときは出力と一致させる
- 「*」(秘匿)を含む行は件数を必ず報告する
- 測地系(JGD2000 / JGD2011)をファイル名と出力に明記する集計後は、検算の指示を別に出します。たとえば次のように頼みます。
半径内の人口(総数)の合計と、年齢層別の合計を比べてください。
差が出た候補地では、秘匿を含む行のメッシュコードを一覧にし、
年齢「不詳」で説明できる分と、秘匿の合算で説明できる分を分けて報告してください。こう頼めば、出力の差を「バグ」と決めつけず、秘匿と年齢不詳のどちらの影響かを切り分けさせられます。プロジェクト単位の規約の置き方は、Claude Codeの全体像にもまとめています。
比較表にする前に潰す4つの落とし穴
落とし穴
年齢別の合計が総数と合わない
国勢調査の地域メッシュ統計は、一定規模以下の区画で結果を秘匿し、「*」と表示します。秘匿した数値は、一定の規則で決めた合算先のメッシュに足しあげられます。統計局のQ&Aは、合算先では秘匿しない項目とその内訳の合計が一致せず、人口総数より年齢別人口の合計が大きくなる例を挙げています。人口総数、男、女、世帯総数、一般世帯数、一般世帯人員は秘匿の対象外です。
もう一つの原因が年齢不詳です。人口(総数)には年齢が「不詳」の人も含まれるため、秘匿がまったくない区画でも、5歳階級の合計は総数より小さくなることがあります。秘匿の合算で大きくなる側と、年齢不詳で小さくなる側が重なるので、差の向きは区画ごとに違います。
次の3点も確認します。
- 測地系: 令和2年国勢調査からJGD2011の結果が加わり、同じメッシュコードでも区画の範囲が違う場合があります。差があるのは、東京都、神奈川県、埼玉県など20都県です。JGD2000とJGD2011を混ぜて集計しない
- 人が住んでいない区画: 人口が存在しないメッシュは、レコードそのものがありません。「0」ではなく行が無いので、結合の結果が空になっても異常ではない
- 昼間人口ではない: 国勢調査のメッシュ統計は常住地ベースです。駅前の商業立地では、昼間にその場にいる人数と大きく乖離します。昼間人口の国勢調査集計は市区町村単位までで、メッシュ別に推計していた昼間人口は現在作成されていません
最後の点は、商圏分析で見落としやすい制約です。住宅地への出店なら夜間人口の比較が素直に使えます。オフィス街や駅前では、経済センサスの従業者数のメッシュ統計を並べて補う手があります。経済センサスのメッシュ統計は事業所の所在地から区画を決めており、国勢調査とは区画への割り当て方が異なります。
候補地の比較表の形
集計結果は、候補地を列、指標を行にすると読みやすくなります。次は形の例で、数値は仮です。
| 指標 | A駅前 | B住宅街 |
|---|---|---|
| 総人口(半径1km) | A駅前12,000 | B住宅街18,500 |
| 0〜14歳の割合 | A駅前8% | B住宅街15% |
| 15〜64歳の割合 | A駅前72% | B住宅街60% |
| 65歳以上の割合 | A駅前20% | B住宅街25% |
| 秘匿を含む行数 | A駅前3 | B住宅街11 |
割合を出すときの分母は、秘匿されない総数を使う選択肢があります。年齢別の合計は秘匿の合算で歪むためです。ただし総数には年齢不詳が含まれるので、3区分の割合を足しても100%にならないことがあります。年齢層どうしの構成比を見たいときは、3区分の合計を分母にする方法もあり、どちらを使ったかを表に書き添えます。表の末尾に秘匿件数を載せておくと、数字の確かさを読み手が自分で判断できます。
PLATEAUのCityGMLから建物属性を取り出す手順を組み合わせれば、候補地周辺の建物の用途や階数も同じ座標で並べられます。人口の表と建物の表を同じ候補地キーで結ぶだけで、住宅中心の街かオフィス中心の街かの裏づけが増えます。
まとめ
商圏人口の比較は、年齢別の人数そのものより、秘匿と常住地ベースという2つの前提を表に書き添えられるかで信頼度が決まります。座標計算と足し上げはClaude Codeに任せられますが、列名と測地系は定義書と先頭行で自分で確定させます。市区町村より細かい単位が要るときに限り、地域メッシュ統計の出番が来ます。