Lesson 03

避難所4,065件 JSON→DataFrame — 構造探索・共起・分布・地理

導入基礎心得v2-rewrite
所要 90分 / 想定レベル: リテラシ基礎 / データ: 避難所情報 (DoBoX #42)

学習目標

使用データ

データ取得手順

論題データセットDL保存先形式サイズ
避難所情報 (4,065 施設)DoBoX #42ページから DL ボタンdata/shelters.jsonJSON (items 配列, UTF-8)約 4 MB

一括取得(全レッスン共通, 推奨):

cd "2026 DoBoX 教材"
py -X utf8 data\fetch_all.py

fetch_all.py はカタログ・追加データを data/data/extras/ に再現可能ダウンロード。DoBoX のオープンデータは申請不要、商用・非商用とも利用可。本レッスンの .py スクリプトは、データが無ければ自動取得してから処理を始めるよう実装されていますensure_dataset() ヘルパ)。

スクリプト(全体ソースコード)

⬇ L03_shelter_analysis.py

cd "2026 DoBoX 教材"
python -X utf8 lessons/L03_shelter_analysis.py

方法

  1. JSON 読込 → 平坦化: json.loadpd.json_normalize(raw["items"]) で 4,065 × 36 の DataFrame を得る。latitude / longitude / capacity は文字列で来るため pd.to_numeric(errors="coerce")
  2. 構造プロファイル: 各列の dtype・null率・ユニーク数を 1 表にまとめる ('field profiling' = データ理解の最初の一歩)
  3. 5 災害種別フラグ{0,1} に整数化し、件数・カバレッジ率を集計
  4. Jaccard 共起行列: |A∩B| / |A∪B| を 5×5 で計算 → ヒートマップ。同時対応のしやすさが定量化される
  5. capacity 分布: scipy.stats.lognorm.fit で μ_log, σ_log を推定 → ヒストに pdf を重ね、Q-Q プロットで末尾の乖離を可視化
  6. 市町村集約: groupby("municipalityName") で件数・capacity合計・BFスコア中央値 → 散布 + 上位 15 バーで二側面提示
  7. バリアフリー総合スコア: 11 フラグの >=1 を集計 (0〜11) → 件数上位 12 市町村で箱ひげ比較
  8. folium 地図: 主災害カテゴリ (優先順 津波→土砂→洪水→高潮→地震) で 5 色に塗り分け、MarkerCluster で 4,065 点を破綻なく描画

コード解説

L03_shelter_analysis.py 行 19–433

 1
 2
 3
 4
 5
 6
 7
 8
 9
82
83
84
85
86
87
88
89
90
91
92
93
76
77
78
79
80
81
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import json
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import folium
from folium.plugins import MarkerCluster
from _common import ensure_dataset

# === (0) データ自動取得 ===
ensure_dataset("data/shelters.json", dataset_id=42, label="避難所情報 #42")

# === (1) JSON → DataFrame: json_normalize で平坦化 ===
with open("data/shelters.json", encoding="utf-8") as f:
    raw = json.load(f)
df = pd.json_normalize(raw["items"])           # (4065, 36)
for c in ["latitude", "longitude", "capacity"]:
    df[c] = pd.to_numeric(df[c], errors="coerce")

# === (2) 災害種別 5 フラグを整数化 ===
DISASTER = {"洪水":"floodShFlg", "土砂":"sedimentDisasterShFlg",
            "高潮":"stormSurgeShFlg", "地震":"earthquakeShFlg",
            "津波":"tsunamiShFlg"}
for k, c in DISASTER.items():
    df[k] = (df[c].astype(str) == "1").astype(int)

# === (3) フィールド プロファイリング ===
profile = pd.DataFrame([{
    "field": c, "dtype": str(df[c].dtype),
    "null率(%)": round(df[c].isna().mean()*100, 1),
    "ユニーク数": df[c].nunique(),
} for c in df.columns])

# === (4) Jaccard 共起行列: |A∩B| / |A∪B| ===
keys = list(DISASTER)
J = np.zeros((5, 5))
for i, a in enumerate(keys):
    for j, b in enumerate(keys):
        inter = ((df[a]==1) & (df[b]==1)).sum()
        union = ((df[a]==1) | (df[b]==1)).sum()
        J[i, j] = inter / union if union else 0
# imshow で 5x5 ヒートマップ → ファイル保存

# === (5) capacity の対数正規フィットと Q-Q プロット ===
cap = df["capacity"].dropna(); cap = cap[cap > 0]
shape, loc, scale = stats.lognorm.fit(cap, floc=0)
mu, sigma = np.log(scale), shape       # log-平均と log-SD
# 観測 vs 理論の Q-Q
log_sorted = np.sort(np.log(cap.values))
n = len(log_sorted)
theor = stats.norm.ppf((np.arange(1, n+1) - 0.5) / n, loc=mu, scale=sigma)
plt.scatter(theor, log_sorted, s=6)        # 直線に乗れば lognormal が良 fit

# === (6) 市町村集約: 件数 × capacity合計 × BFスコア ===
muni = df.groupby("municipalityName").agg(
    n=("name", "size"),
    cap_total=("capacity", "sum"),
    bf_med=("bf_score", "median"),
).sort_values("n", ascending=False)

# === (7) folium: 主災害カテゴリで 5 色に塗り分け ===
PRIORITY = [("津波","#cf222e"), ("土砂","#fb8500"), ("洪水","#0969da"),
            ("高潮","#8250df"), ("地震","#1f883d")]
def primary(row):
    for k, c in PRIORITY:
        if row[k] == 1: return c
    return "#999"
df["color"] = df.apply(primary, axis=1)
m = folium.Map([34.4, 132.5], zoom_start=9, tiles="cartodbpositron")
mc = MarkerCluster(disable_clustering_at_zoom=13).add_to(m)
for _, r in df.dropna(subset=["latitude","longitude"]).iterrows():
    folium.CircleMarker([r.latitude, r.longitude], radius=3.5,
        color=r.color, fill=True, fill_color=r.color,
        fill_opacity=0.78).add_to(mc)
m.save("lessons/assets/L03_map.html")

結果

(1) 全フィールド プロファイル

36 列のうち shelterId / shelterStartTimestamp / shelterEndTimestamp は 100% null (開設状況の予約フィールド)。capacity の null は 535 件 (13%) — 「収容力不明」の施設が一定数ある。

field dtype null率(%) ユニーク数
facilityId object 0.0 4065 00006319
name object 0.0 4020 中央公園広場エリア
capacity float64 13.2 799 1500.0
address01 object 0.0 3556 広島市中区基町15
address02 object 0.0 1
latitude float64 0.0 4040 34.40121
longitude float64 0.0 4044 132.45569
shelterAdmId object 0.7 4020
floodShFlg object 0.0 2 0
sedimentDisasterShFlg object 0.0 2 0
stormSurgeShFlg object 0.0 2 0
earthquakeShFlg object 0.0 2 1
tsunamiShFlg object 0.0 2 1
municipalityCd object 0.0 30 341011
municipalityName object 0.0 30 広島市中区
shelterId object 100.0 0 -
shelterStartTimestamp object 100.0 0 -
shelterEndTimestamp object 100.0 0 -
westernFlg object 0.0 3 0
westernNum float64 60.2 74 21.0
japaneseFlg object 0.0 3 0
japaneseNum float64 77.4 52 10.0
handicappedFlg object 0.0 2 0
ostomateFlg object 0.0 2 0
petFlg object 0.0 4 0
parkingFlg object 0.0 3 0
parkingNum float64 23.9 155 0.0
internetFlg object 0.0 3 0
bathFlg object 0.0 4 0
breastfeedingFlg object 0.0 2 0
powerFlg object 0.0 2 0
cookingFlg object 0.0 2 0
heatingFlg object 0.0 2 0
coolongFlg object 0.0 2 0
crowdedStatus object 0.0 1 9
comment object 0.0 1
5 災害種別カバレッジ。最多は洪水 3,071 件 (75.5%)、最少は津波 1,340 件 (33.0%)。津波が極端に少ないのは沿岸市町に偏在しているため
5 災害種別カバレッジ。最多は洪水 3,071 件 (75.5%)、最少は津波 1,340 件 (33.0%)。津波が極端に少ないのは沿岸市町に偏在しているため
災害種別間 Jaccard 共起行列。洪水×土砂が約 0.59 と最も重なり大、津波は他カテゴリと小さい (海岸線の地理的制約)
災害種別間 Jaccard 共起行列。洪水×土砂が約 0.59 と最も重なり大、津波は他カテゴリと小さい (海岸線の地理的制約)
(左) 収容人数の log10 ヒストに対数正規 fit (μ=4.78, σ=1.54, 中央値 119 人) を重ね描き/(右) Q-Q プロット — 中央域は直線にほぼ乗るが裾 (大規模施設) は乖離。「全体は対数正規だが極大はべき的」という現象を可視化
(左) 収容人数の log10 ヒストに対数正規 fit (μ=4.78, σ=1.54, 中央値 119 人) を重ね描き/(右) Q-Q プロット — 中央域は直線にほぼ乗るが裾 (大規模施設) は乖離。「全体は対数正規だが極大はべき的」という現象を可視化
(左) 市町村別: 件数 × 収容人数合計の散布。広島市・福山市・東広島市が圧倒的/(右) 上位 15 市町村のスタックバー (青=件数, オレンジ=収容人数(千人))
(左) 市町村別: 件数 × 収容人数合計の散布。広島市・福山市・東広島市が圧倒的/(右) 上位 15 市町村のスタックバー (青=件数, オレンジ=収容人数(千人))
件数上位 12 市町村のバリアフリー総合スコア (0〜11) 箱ひげ。中央値は概ね 1〜2 と低く、市町村間で分布の散らばり方が違う点に注目
件数上位 12 市町村のバリアフリー総合スコア (0〜11) 箱ひげ。中央値は概ね 1〜2 と低く、市町村間で分布の散らばり方が違う点に注目

(6) 上位 15 市町村 集計表

n cap_total cap_med flood sediment tsunami bf_med
municipalityName
呉市 518 100239.0 95.0 347 217 284 0.0
福山市 474 120842.0 330.0 220 235 175 0.0
東広島市 279 20704.0 25.0 269 227 26 0.0
尾道市 273 317094.0 600.0 247 163 229 0.0
庄原市 247 40116.0 30.0 222 198 0 0.0
広島市安佐南区 156 62374.0 79.0 127 104 0 0.0
広島市安佐北区 153 32383.0 66.5 128 93 0 0.0
広島市西区 136 35840.0 63.5 103 96 18 0.0
江田島市 131 24663.0 65.0 123 91 74 0.0
三原市 128 130531.0 101.0 91 85 109 2.0
広島市南区 122 53049.0 71.5 106 92 5 0.0
府中市 120 229132.0 142.0 89 72 0 0.0
廿日市市 120 66827.0 72.0 109 81 67 1.0
広島市佐伯区 117 32273.0 128.0 99 81 6 0.0
北広島町 102 14351.0 94.0 57 67 0 0.0

(7) 避難所マップ — 主災害カテゴリ別カラー

画面拡大で個別マーカーが現れる (zoom 13 以上で MarkerCluster 解除)。クリックで施設名・住所・収容人数・BFスコア・対応災害種別を表示。

考察

発展課題

  1. 人口あたり指標: e-Stat で広島県下市町村の人口を取り、「人口千人あたり収容力」を市町村別に計算 → 散布図上での「過剰/不足」を可視化。
  2. クラスタリング: 災害種別 5 フラグ + BF スコア + log(capacity) を特徴量として k-means / HDBSCAN で施設を類型化し、地図上で色分け。「沿岸特化型」「内陸大規模型」などの自然なグループが浮かぶか検証。
  3. ハザードマップとの突合: DoBoX #46 (津波浸水想定) と緯度経度で空間結合し、「津波対応フラグが立っているのに浸水想定域にある」避難所を抽出。
  4. capacity の欠損補完: 535 件の null を、同一市町村・同一施設種別の中央値で補完して合計収容力を再評価し、補完前後で結論が変わるかを見る。
  5. バリアフリースコアの主成分分析: 11 フラグの相関構造を PCA で抽出し、第 1 主成分が「設備充実度」、第 2 主成分が「想定災害弱者種別」を分離するかを検証。