Lesson 80

X08: 河川浸水想定区域 × 避難所立地 — 「避難所自体が水没するか?」 点 in ポリゴン判定研究

X-tier点 in ポリゴン判定研究Ray castingbbox プリフィルタ避難所浸水想定区域危険避難所L01
所要 60-90分 / 想定レベル: リテラシレベル (L01) / データ: 避難所 4,065 件 (DoBoX #42 JSON) + 浸水想定 計画規模 416 ポリゴン + 想定最大規模 613 ポリゴン (DoBoX 直 DL)

データ取得手順

⚠️ このスクリプトは自動取得に対応していません。以下のデータセットを DoBoX から手動でダウンロードし、data/extras/ 以下に保存してください。

IDデータセット名
#30dataset #30
#42避難所情報
#43高潮浸水想定区域情報_30年確率
#45高潮浸水想定区域情報_想定最大規模
#46津波浸水想定区域情報
#222dataset #222
#295河川浸水想定区域情報_計画規模_全河川
#313河川浸水想定区域情報_想定最大規模_全河川
#333dataset #333
#444dataset #444
#888都市計画区域情報_区域データ_安芸高田市_行政区域
#1278過去に発生した災害情報
#1351都市計画区域情報_宅地開発状況_安芸高田市_2016-2020
#1641多段階の浸水想定図

実行コマンド:

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

DoBoX のオープンデータは申請不要・商用/非商用とも利用可。 data/extras/.gitignore 対象(約 57 GB のキャッシュ)。 スクリプト実行で自動再生成されます。

学習目標と問い

【本記事のスタイル: 点 in ポリゴン判定研究】 4,065 避難所のうち どれが 浸水想定域内に立地しているかを 1 件ごとに Ray casting 法で判定し、危険避難所をランキング化する。 X07 が「ポリゴン × ポリゴンの面積率研究」だったのに対し、 本記事は「点 × ポリゴン」という別軸の空間結合。 PCA・散布図行列・ロジ回帰・k-means は使わず、 危険避難所ランキング棒・市町別棒・ボーダー判定棒・フラグ×立地クロス・標高散布 の 5 図を主役に据える。

本レッスンは 避難所点 4,065 件 (DoBoX #42)河川浸水想定区域 (計画規模・想定最大規模 各 1 件, DoBoX #295/#313)同一の経緯度系 (WGS84) に揃え、 4,065 × 2 規模 = 8,130 件の点判定を行う。 得られた判定は次の 5 つの仮説と照合する。

このレッスンで答えたい問い (1 文)

「広島県内の避難所 4,065 件のうち何件が河川浸水想定区域内に立地しており、 そのうちで 本当に避難してはいけない 危険避難所はどこか?」

立てた仮説 H1〜H5

独自定義の用語 (このレッスン専用 — 要件M)

到達点

  1. Ray casting で 1 点が 1 多角形の内側か外側かを判定できる (= 1 関数 10 行)。
  2. 4,065 点 × 1,029 ポリゴンの総当たり判定を bbox プリフィルタで 秒単位に高速化できる。
  3. 異なる 座標系 (CRS) の点とポリゴンを、共通の経緯度系に揃えることで判定を統一できる。
  4. 避難所自体が水没する」という反直観的な事実を、4,065 件の数字で確認できる。
  5. 計画規模 vs 想定最大規模の差を 「ボーダー避難所」として可視化し、 通常運用では見えない過去最大級リスクを発見できる。

なぜ「危険スコア」を主指標に選んだか (要件 H/I)

候補指標長所限界判定
(A) in_max のみ (二値)解釈最簡水深と標高の差を区別できない補助指標
(B) 危険スコア (in_max × 標高ペナルティ) ★連続値で順位付け可能標高はシンボリック近似採用 (主指標)
(C) 浸水深 × 標高差物理的に正確浸水深データが本シェープにない不採用
(D) 容量加重スコア政策的に重要capacity 欠損 535/4,065派生指標として採用

(B) を主指標、(A)(D) を補助指標とする。 浸水深の代替として 「両規模で水没 (both_in)」を 1.5 倍重み付けで取り込み、 「常時危険」と「最大規模時のみ危険」を順位上で区別している。

使用データ

本レッスンは DoBoX 3 系統を使う。 避難所は 4,065 件すべて、浸水は 39 件のうち 「全河川」集約版 2 件のみを使う (個別水系 37 件は X07 と同じ理由でメタ集計のみ)。

原データ — 避難所情報 (1 件, 4,065 行)

ID名称形式件数主要列
#42避難所情報JSON4,065 name, latitude, longitude, municipalityName, floodShFlg, capacity

原データ — 河川浸水想定区域情報 (39 件のうち 2 件を使用)

ID名称形式規模ポリゴン数
#295計画規模 全河川Shapefile 計画規模 (1/100〜1/30)416
#313想定最大規模 全河川Shapefile 想定最大規模 (1/1000)613

派生データ — 市町別代表標高 (シンボリック)

本記事では浸水深の 絶対値データが Shapefile に含まれないため、 標高との比較は 市町ごとの代表標高 (役所所在地付近, m, 整数概算) を ローカル定数として定義する手法を採る。 30 市町を網羅、未定義は 50 m とフォールバック。 これは シンボリック値であり、避難所 1 件ごとの実標高ではない点に注意 (= 限界)。

サイズ・次元の整理 (要件 L)

行/列/サイズ役割
原 避難所 JSON4,065 行 × 30 列 1 行 = 1 避難所 (ID, 名称, lat, lon, 各種フラグ)
原 浸水 Shapefile (計画+最大) 416 + 613 = 1029 ポリゴン 1 行 = 1 浸水ポリゴン
判定後 shel_df 4,065 行 × 22 列 各避難所に in_keikaku, in_max, only_max_in, border_class, danger_score を追加
市町別集計 city_summary 30 行 × 9 列 市町ごとに件数集計 (主集計テーブル)
判定総回数 (粗計算) 4,065 点 × (1029) ポリゴン = 4,182,885 回 bbox プリフィルタなしの上限

※ 表示の都合で「上位 30」「上位 20 市町」と書くが、 全件は 常に 4,065 避難所 / 30 市町で集計済み (要件L)。

ダウンロード (再現用データ・中間データ・図)

原データ (DoBoX, 直リンク・直 DL)

論題データセットDL保存先形式サイズ
避難所情報DoBoX #42ページから DL ボタンdata/shelters.jsonJSON~3 MB
計画規模 全河川DoBoX #295直DLdata/extras/flood_shp/shinsui_keikaku.zipShapefile (zip)22 MB
想定最大規模 全河川DoBoX #313直DLdata/extras/flood_shp/shinsui_souteisaidai.zipShapefile (zip)38 MB

個別取得(PowerShell, このレッスンだけ):

cd "2026 DoBoX 教材"
iwr "https://hiroshima-dobox.jp/resource_download/23061" -OutFile "data/extras/flood_shp/shinsui_keikaku.zip"
iwr "https://hiroshima-dobox.jp/resource_download/23118" -OutFile "data/extras/flood_shp/shinsui_souteisaidai.zip"

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

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

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

本レッスン生成の中間データ (HTML から直 DL)

★ 河川浸水想定区域 dataset 39件のカバーについて
DoBoXには河川浸水想定区域情報が 39 dataset_id 公開されています:
  • 計画規模 19件: 全河川版 (#295) + 個別18水系 (#35 太田川 / #157 江の川 / #279 芦田川 / #280 沼田川 ほか) + 単独河川
  • 想定最大規模 20件: 全河川版 (#313) + 個別18水系 + 中小河川ブロック
本記事は 全河川版 Shapefile (#295 + #313) を使用。これは各個別水系 dataset_id の スーパーセット として配布されており、suikei列でフィルタすれば個別水系の中身を完全再現できます (例: flood_max[flood_max['suikei']=='太田川水系'] で #36 と等価)。 したがって本記事は 河川浸水想定区域 39 件全部を論理カバー しています。 個別水系特化の深掘り研究 (M1 太田川 / M2 江の川 / M3 芦田川 / M4 沼田川 / M5 黒瀬川) は今後の発展課題です。

図 PNG (HTML から直 DL)

再現スクリプト

X08_flood_shelter_in_polygon.py を以下で実行:

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

初回実行時に data/extras/flood_shp/ へ 浸水 2 件を自動取得する。所要 30〜60 秒 (DL は逐次)。

分析1: 4,065 避難所と 2 規模浸水を経緯度系で揃える (要件K 1件追跡)

狙い

避難所点 (lat, lon) は WGS84 経緯度で配信されているが、 浸水 Shapefile は EPSG:3857 (Web Mercator) 単位 m で配信されている。 両者を WGS84 経緯度に揃えて重ね合わせる。 本記事では geopandas/pyproj を使わず、球面近似式 1 本だけで変換する。

手法 (要件 B/J: ツール化視点で簡潔に)

STEP 分け (要件 O)

役割入力出力
STEP1: Ray casting1 点が 1 多角形の内側か(x, y, ring)True/False
STEP2: bbox プリフィルタ外接矩形外を即外側判定点 + ポリゴン bbox候補のみ抽出
STEP3: ベクトル化判定4,065 点を一括処理numpy 配列bool 配列
STEP4: 規模ごとに繰り返し計画/最大 で 2 周flood_data dictin_keikaku, in_max

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import math
import numpy as np

WGS_R = 6378137.0

def merc_to_ll(x, y):
    """Web Mercator (EPSG:3857) → WGS84 経緯度 (厳密式)"""
    lon = math.degrees(x / WGS_R)
    lat = math.degrees(2 * math.atan(math.exp(y / WGS_R)) - math.pi/2)
    return lon, lat

def points_in_ring_vec(xs_pt, ys_pt, ring_xs, ring_ys):
    """N 点が 1 リング (M 頂点) の内側かをベクトル化 Ray casting で判定。"""
    n_pts = len(xs_pt)
    inside = np.zeros(n_pts, dtype=bool)
    m = len(ring_xs)
    j_idx = (np.arange(m) - 1) % m
    for i in range(m):
        yi, yj = ring_ys[i], ring_ys[j_idx[i]]
        xi, xj = ring_xs[i], ring_xs[j_idx[i]]
        cond_y = (yi > ys_pt) != (yj > ys_pt)         # y 方向の交差検査
        if not cond_y.any():
            continue
        x_int = (xj-xi) * (ys_pt-yi) / (yj-yi+1e-15) + xi
        inside ^= cond_y & (xs_pt < x_int)            # XOR で内外を反転
    return inside

結果 (表と読み取り) — 海田町 1 件追跡 (要件K)

「JSON 取得 → 1 件選択 → 経緯度抽出 → 計画判定 → 最大判定 → 標高 → ボーダー区分 → フラグ確認 → 危険スコア」の 9 段階 を、海田町民センター 1 件で具体値を追って示す。

表1: 海田町 海田町民センター 段階表 (Before → After)

段階 内訳
1. JSON 取得 DoBoX #42 避難所情報 (4,065 件) data/shelters.json から1件抽出
2. 1件選択 海田町民センター 市町=海田町 / 住所=安芸郡海田町寺迫1-1-29
3. 経緯度抽出 (132.54933, 34.37294) JSON の latitude / longitude 文字列を float 化
4. 計画規模 判定 OUT Ray casting × 416 ポリゴン
5. 想定最大規模 判定 IN Ray casting × 613 ポリゴン
6. 標高 lookup 4 m 市町別代表標高 (海田町 = 4 m)
7. ボーダー区分 C ボーダー (最大規模のみ水没) (計画, 最大) の 4 区分パターン
8. 洪水対応フラグ JSON の floodShFlg (DoBoX 側の災害種別フラグ)
9. 危険スコア 0.867 in_max × (1 + 0.5 × both_in) × clip(1 - elev/30, 0.05, 1.0)

この表から読み取れること:

分析2: 点 in ポリゴン判定 (Ray casting + bbox プリフィルタ) (要件J)

狙い

避難所点 4,065 件が 計画規模 416 ポリゴン想定最大規模 613 ポリゴンのどれかに入っているかを、 ライブラリ (geopandas/shapely) を使わずに 純 Python + numpy だけで判定する。

手法 (要件B/J)

直感的説明 — Ray casting

判定したい点 P から右方向に半直線 (光線, ray) を伸ばし、ポリゴン外周との 交差回数を数える。 奇数なら P は内側、偶数 (0 含む) なら外側。 これは「外周を 1 回横切るたびに内→外、または外→内」という単純な原理に基づく。 凹多角形でも穴がない限り正しく動作する。

アルゴリズム (4 ステップ)

  1. 避難所点を numpy 配列化: xs = np.array([s['lon'] for s in shelters]) 等。
  2. 規模ごとにポリゴンをループ: 計画 → 最大 の 2 周。
  3. 各ポリゴンで bbox プリフィルタ: ポリゴン bbox 外の点は確定で外側 → Ray casting スキップ。
  4. 残った候補に対してリング単位で Ray casting: 1 ポリゴンに複数のリング (parts) がある 場合は 奇偶 XOR で内外を集約。

入出力の形 (=このツールで何ができるか)

計算量と高速化

限界 (=このツールで何ができないか)

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def points_in_any_polygon_set(xs_pt, ys_pt, polys):
    """N 点 vs P ポリゴン集合 → bool 配列。bbox プリフィルタ付き。"""
    n_pts = len(xs_pt)
    inside = np.zeros(n_pts, dtype=bool)
    for p in polys:
        # bbox プリフィルタ (このポリゴンの bbox 外なら確定で外)
        cand = (~inside) & \
               (xs_pt >= p["lon_min"]) & (xs_pt <= p["lon_max"]) & \
               (ys_pt >= p["lat_min"]) & (ys_pt <= p["lat_max"])
        if not cand.any():
            continue
        idx = np.where(cand)[0]
        sub_x, sub_y = xs_pt[idx], ys_pt[idx]
        sub_in = np.zeros(len(idx), dtype=bool)
        for ring_x, ring_y in p["rings"]:
            still = ~sub_in
            if not still.any():
                break
            r = points_in_ring_vec(sub_x[still], sub_y[still], ring_x, ring_y)
            sub_in[still] |= r
        inside[idx] |= sub_in
    return inside

# 4,065 避難所 × 2 規模を一括判定
xs = shel_df["lon"].values.astype(np.float64)
ys = shel_df["lat"].values.astype(np.float64)
shel_df["in_keikaku"] = points_in_any_polygon_set(xs, ys, flood_data["計画規模"]).astype(int)
shel_df["in_max"]     = points_in_any_polygon_set(xs, ys, flood_data["想定最大規模"]).astype(int)

結果 (表と読み取り) — 4,065 × 2 規模 判定サマリ

規模浸水域内件数占有率処理時間 (参考)
計画規模540 13.3%~5 秒
想定最大規模1,552 38.2%~10 秒

この表から読み取れること:

分析3: 主役図 — 危険避難所 上位 30 マルチ属性ランキング (要件H)

狙い

4,065 件のうち想定最大規模浸水域内に立地する 1552 件をすべて並べると 横棒が長くなりすぎる。「危険スコア」上位 30を抽出し、1 枚に複数属性を圧縮表示する。 本記事の 主役図。学習者は他の図を見る前に、この図で 「具体的にどの避難所が一番危なく、なぜそれが危ないのか (低地? 大規模? 洪水避難所未指定?)」を地名レベルで把握する。

旧版 (赤濃淡だけ) の問題点

旧 図1 は danger_score をバー長と赤濃度で表現したが、 上位 30 はほぼ全件が score > 0.85 の「高スコア帯」に集中するため バーがほぼ同じ長さ・同じ赤色になり、視覚的に区別がつきにくかった。 1 枚に 1 指標では情報密度が低い。

新版: マルチ属性化 (1 枚に 5 軸)

視覚チャネルマッピング先狙い
バー長 (x 軸)capacity (収容人数)「同じ危険でも何人巻き込まれるか」を即座に把握
バー色 (カテゴリ)市町 (tab20 カラー)同じ市町の連続出現 = 「危険集中エリア」が一目で分かる
右マーカー ★flood_flg = 0 (洪水避難所未指定)「制度上は洪水避難所ではないのに浸水域内」= 運用矛盾の典型
右マーカー ▲elev_m ≦ 5m (低地)河口・デルタ平野立地の即時識別
右マーカー ●5 災害フラグ全立 (複合避難所)「全災害対応」を看板に掲げた避難所が河川氾濫で水没する皮肉
右数値テキスト標高 / capacity / score具体値の確認

なぜマルチ属性が必要か (4 案比較)

方式長所限界判定
(A) 旧版: 赤濃淡 score バーシンプル, 順位は明快上位30 がほぼ同色 → 識別不能不採用 (情報密度不足)
(B) capacity バー × 市町色 × 補助マーカー ★5 軸を 1 枚で表示, 市町集中も即見えるマーカー記号の凡例が必要採用 (新主役)
(C) 散布図 (標高×score)2 変数同時名前ラベルが重なる, 順位感がない不採用
(D) 並列ボード 3 パネル ★各属性を分離して比較1 枚に集約しないので「全体感」がない図1B として採用 (補助)

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import matplotlib.pyplot as plt
import numpy as np

danger = shel_df[shel_df["in_max"] == 1].sort_values("danger_score", ascending=False)
top = danger.head(30).iloc[::-1].reset_index(drop=True)
ypos = np.arange(len(top))

# capacity 欠損は中央値補完 (バー長 0 を回避)
cap_med = float(shel_df["capacity"].dropna().median())
top["cap_disp"] = top["capacity"].fillna(cap_med).astype(float)

# 市町 → カテゴリカル色
unique_munis = list(top["muni"].unique())
cmap_muni = plt.get_cmap("tab20")
muni_color = {m: cmap_muni(i % 20) for i, m in enumerate(unique_munis)}
bar_colors = [muni_color[m] for m in top["muni"]]

FLAG_COLS = ["flood_flg", "sediment_flg", "storm_flg", "eq_flg", "tsunami_flg"]
top["n_hazard"] = top[FLAG_COLS].sum(axis=1)

fig, ax = plt.subplots(figsize=(13, 10))
ax.barh(ypos, top["cap_disp"], color=bar_colors, edgecolor="#222", linewidth=0.5)

xmax = top["cap_disp"].max()
for i, (_, r) in enumerate(top.iterrows()):
    barlen = r["cap_disp"]
    if r["flood_flg"] == 0:
        ax.text(barlen + xmax*0.015, ypos[i], "★", va="center", ha="center",
                fontsize=14, color="#cf222e", fontweight="bold")
    if r["elev_m"] <= 5:
        ax.text(barlen + xmax*0.05, ypos[i], "▲", va="center", ha="center",
                fontsize=12, color="#bf5700")
    if r["n_hazard"] == 5:
        ax.text(barlen + xmax*0.085, ypos[i], "●", va="center", ha="center",
                fontsize=12, color="#1a7f37")
    ax.text(barlen + xmax*0.13, ypos[i],
            f"標高{r['elev_m']:.0f}m  {int(r['cap_disp']):,}人  "
            f"score={r['danger_score']:.2f}",
            va="center", fontsize=8.2, color="#333")

結果 (図と読み取り) — 主役図

図1 (主役): 危険避難所 上位 30 マルチ属性ランキング — バー長=収容力 / バー色=市町 / ★=洪水フラグ無 / ▲=標高≦5m / ●=5災害フラグ全立
図1 (主役): 危険避難所 上位 30 マルチ属性ランキング — バー長=収容力 / バー色=市町 / ★=洪水フラグ無 / ▲=標高≦5m / ●=5災害フラグ全立

表2: 危険避難所 上位 15 (危険スコア降順)

避難所名 市町 標高(m) 計画 最大 洪水対応 危険スコア
南千田西集会所 広島市中区 3.0 IN IN 1.35
千田児童館 広島市中区 3.0 IN IN 1.35
中島小学校 広島市中区 3.0 IN IN 1.35
男女共同参画推進センター(ゆいぽーと) 広島市中区 3.0 IN IN 1.35
袋町学区会館(国泰寺集会所) 広島市中区 3.0 IN IN 1.35
国泰寺中学校 広島市中区 3.0 IN IN 1.35
鷹野橋職員会館 広島市中区 3.0 IN IN 1.35
中区スポーツセンター(コジマホールディングス中区スポーツセンター) 広島市中区 3.0 IN IN 1.35
砂走公園 海田町 4.0 IN IN 1.30
海田町立海田中学校 海田町 4.0 IN IN 1.30
海田町立海田西小学校 海田町 4.0 IN IN 1.30
海田町立海田西中学校 海田町 4.0 IN IN 1.30
海田町立海田小学校 海田町 4.0 IN IN 1.30
海田東公民館 海田町 4.0 IN IN 1.30
西浜公園 海田町 4.0 IN IN 1.30

この図と表から読み取れること (5 軸を統合した読み):

図1B (補助): 上位30 を 3 軸並列展開

図1 は 1 枚に 5 軸を圧縮しているため、属性ごとの比較には別の見方が要る。 同じ上位 30 の 行並びを揃えて 3 パネルに分けると、 「順位は高いが capacity は小さい」「順位は中位だが標高 0m」「災害フラグが洪水のみ」 といった 属性間の不一致が一目で分かる。

図1B (補助): 上位30 を 3 パネル並列 (左=capacity[市町色] / 中=標高[低いほど赤] / 右=5災害フラグ heatmap)
図1B (補助): 上位30 を 3 パネル並列 (左=capacity[市町色] / 中=標高[低いほど赤] / 右=5災害フラグ heatmap)

図1B から読み取れること:

分析4: 市町別 危険避難所件数ランキング (H5 検証)

狙い

図1 は個別避難所の順位だが、政策単位は市町。 市町ごとに「危険避難所が何件あるか」を集計し、上位 20 を棒グラフで並べる。 これにより 市町行政の備えるべき件数が明示される。

手法

結果 (表と読み取り)

表3: 市町別 集計 上位 20

市町 全避難所 想定最大域内 計画域内 ボーダー(C) 最大域内率(%) 洪水対応有 市町平均標高(m)
福山市 474 264 142 122 55.70 220 7.0
呉市 518 134 16 118 25.87 347 8.0
広島市南区 122 98 9 89 80.33 106 4.0
広島市中区 88 86 8 78 97.73 75 3.0
広島市西区 136 84 45 39 61.76 103 4.0
広島市安佐南区 156 77 46 31 49.36 127 30.0
東広島市 279 68 8 60 24.37 269 220.0
広島市安佐北区 153 64 30 34 41.83 128 110.0
庄原市 247 57 4 53 23.08 222 230.0
三原市 128 57 29 28 44.53 91 6.0
海田町 67 55 36 19 82.09 46 4.0
広島市佐伯区 117 51 16 35 43.59 99 18.0
府中市 120 46 26 20 38.33 89 80.0
三次市 84 41 24 17 48.81 80 165.0
尾道市 273 39 11 28 14.29 247 5.0
北広島町 102 38 1 37 37.25 57 470.0
広島市東区 89 37 19 18 41.57 75 6.0
広島市安芸区 78 36 6 30 46.15 69 25.0
竹原市 71 33 26 7 46.48 43 5.0
安芸太田町 81 32 10 22 39.51 48 350.0

この表から読み取れること:

結果 (図と読み取り)

図2: 市町別 危険避難所件数 上位 20 (赤=想定最大, 青=計画)
図2: 市町別 危険避難所件数 上位 20 (赤=想定最大, 青=計画)

この図から読み取れること:

分析5: ボーダー避難所 4 区分内訳 (H3 検証)

狙い

計画規模 vs 想定最大規模の 判定差を 4 区分に分解する: A (両方安全), B (両方水没), C (ボーダー = 計画OK / 最大NG), D (異常 = 計画NG / 最大OK)。 本セクションの主役は 区分 C = 「過去最大級降雨で初めて使えなくなる」避難所。

4 区分の意味

区分計画規模想定最大規模意味政策含意
AOUTOUT両規模で安全常時開設可能
BININ両規模で水没洪水対応指定取り消し検討
COUTINボーダー過去最大級時のみ代替必要
DINOUT異常 (理論上稀)判定エラーまたは縮小済み堤防

手法

結果 (表と読み取り)

表4: 4 区分内訳

区分 件数 割合(%)
A 両規模で安全 2513 61.82
B 両規模で水没 540 13.28
C ボーダー (最大規模のみ水没) 1012 24.90

この表から読み取れること:

結果 (図と読み取り)

図3: 4 区分内訳 (左) と 想定最大規模水没のボーダー比率 (右)
図3: 4 区分内訳 (左) と 想定最大規模水没のボーダー比率 (右)

この図から読み取れること:

分析6: 洪水対応フラグ × 立地 クロス、標高 vs 立地 (H4 検証)

狙い

避難所 JSON には floodShFlg という「洪水時に開設対象」を示す 0/1 フラグがある。 このフラグと 点 in ポリゴン判定の整合性を検証する。 仮説 H4: フラグ有 → 立地が安全 (= 浸水域内率が低い) はず。

手法

図4 (フラグ × 立地 クロス)

図5 (標高 vs 立地)

実装

X08_flood_shelter_in_polygon.py 行 372–448

 1
 2
 3
 4
 5
 6
 7
 8
 9
381
382
383
ct = pd.crosstab(
    shel_df["flood_flg"].map({0: "洪水対応無", 1: "洪水対応有"}),
    shel_df["in_max"].map({0: "浸水域外", 1: "浸水域内"}),
    margins=True, margins_name="合計",
)
def expected_chi2(ct):
    obs = ct.iloc[:-1, :-1].values.astype(float)
    row, col, tot = obs.sum(1), obs.sum(0), obs.sum()
    exp = np.outer(row, col) / tot
    return float(((obs - exp)**2 / exp).sum()), exp
chi2, _ = expected_chi2(ct)
print(f"χ² = {chi2:.2f}  (df=1, p<0.001 閾値 ≈ 10.83)")

結果 (表と読み取り)

表5: 洪水対応フラグ × 想定最大規模立地 (2×2 クロス)

in_max 浸水域内 浸水域外 合計
flood_flg
洪水対応有 950 2121 3071
洪水対応無 602 392 994
合計 1552 2513 4065

この表から読み取れること:

結果 (図と読み取り) — 図4 フラグ × 立地

図4: 洪水対応フラグ × 想定最大規模立地 2×2 クロスヒートマップ
図4: 洪水対応フラグ × 想定最大規模立地 2×2 クロスヒートマップ

この図から読み取れること:

結果 (図と読み取り) — 図5 標高 vs 立地

図5: 立地標高 vs 想定最大規模 域内/域外  (左: ジッタ散布, 右: 箱ひげ)
図5: 立地標高 vs 想定最大規模 域内/域外 (左: ジッタ散布, 右: 箱ひげ)

この図から読み取れること:

分析7: 危険度の地理マッピング (4,065 点を danger_score で色塗り)

狙い

分析3〜6 までは「ランキング棒」「集計棒」「クロス表」「ジッタ散布」と 非地理的な可視化だった。しかし「避難所がどこに立地するか」は本質的に地理問題。 4,065 件の (lat, lon) を直接散布し、危険度の連続値 (danger_score) で色塗りすれば、 沿岸/内陸/山間の分布パターンが一目で読める。

なぜこの図か (要件H, 4 案比較)

方式長所限界判定
(A) 全 4,065 点 単色散布地理分布のみ把握danger 強弱が出ない不採用
(B) 浸水域内のみ danger_score で色塗り ★「どこが危険か」が連続色で読める背景に全体感が要る採用
(C) ヒートマップ (KDE)密度を平滑化個別避難所が消える不採用
(D) ベース地図 + マーカー市町境界が見える外部地図ライブラリ依存不採用 (純matplotlib縛り)

手法

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import numpy as np
import matplotlib.pyplot as plt

mask_in = shel_df["in_max"] == 1
fig, ax = plt.subplots(figsize=(11.5, 8.5))

# 背景: 浸水域外を薄く
ax.scatter(shel_df.loc[~mask_in, "lon"], shel_df.loc[~mask_in, "lat"],
           s=6, c="#cccccc", alpha=0.35, edgecolor="none")

# 前景: 浸水域内のみ danger_score で色塗り
sc = ax.scatter(shel_df.loc[mask_in, "lon"], shel_df.loc[mask_in, "lat"],
                s=22, c=shel_df.loc[mask_in, "danger_score"],
                cmap="RdYlGn_r", vmin=0.0, vmax=1.5,
                alpha=0.85, edgecolor="#222", linewidth=0.25)
plt.colorbar(sc, ax=ax, label="danger_score (0=安全 → 1.5=最大危険)")
ax.set_aspect(1 / np.cos(np.deg2rad(34.4)))

結果 (図と読み取り)

図6: 危険度の地理マッピング — 浸水域内のみ danger_score で色塗り (RdYlGn 反転)
図6: 危険度の地理マッピング — 浸水域内のみ danger_score で色塗り (RdYlGn 反転)

この図から読み取れること:

分析8: border_class 別 地理マッピング (A/B/C 沿岸-内陸-山間パターン)

狙い

分析5 (図3) で 4 区分 A/B/C/D の件数を棒グラフで見たが、地理的にどう散らばるかは分からなかった。 本図では同じ 4,065 点を border_class で色分けして 1 枚の地図に重ね、 「B (常時危険) は河口部に / C (ボーダー) は中流部に / A (安全) は山間に」 というクラスタが 目視で読み取れるかを検証する。

なぜこの図か (要件H)

図6 (前節) は連続値 danger_score を色塗りしたため 「程度」は分かるが、 「種類」(両規模水没 vs ボーダー) は区別できない。一方、図3 は 4 区分件数だけで 地理パターンが消える。両者の中間として、離散カテゴリ × 地理座標を重ねる本図を選んだ。

手法

結果 (図と読み取り)

図7: border_class 別 地理マッピング — A=緑(背景) / B=赤(両規模水没) / C=黄(ボーダー) / D=灰(異常)
図7: border_class 別 地理マッピング — A=緑(背景) / B=赤(両規模水没) / C=黄(ボーダー) / D=灰(異常)

この図から読み取れること:

分析9: 5 災害種別フラグ × 浸水域立地のミスマッチ (制度 vs 地形)

狙い

避難所 JSON には 5 種類の災害対応フラグがある (洪水/土砂/高潮/地震/津波)。 分析6 では 洪水フラグ × 浸水域内のみ見たが、「洪水フラグなし」かつ「浸水域内」の避難所は 制度上は洪水避難所ではないが、実は地形的に浸水域に立地する潜在リスク群。 さらに 5 災害分を一括比較すれば、フラグ運用の盲点が見える。

なぜこの図か (要件H)

図4 (フラグ × 立地 2×2 クロス) は洪水フラグだけの分析。 本節では 5 災害種別を横並びで比較し、さらに 制度外なのに浸水内の 602 件を 地図にプロットすることで「制度上の指定」と「地形上のリスク」の乖離を可視化する。

手法

結果 (表と読み取り) — 5 災害種別フラグ集計

表6: 5 災害種別フラグ × 想定最大規模浸水域内立地

災害種別 フラグ列 ミスマッチ(フラグ無×浸水内) 対応有×浸水内
洪水 flood_flg 602 950
土砂 sediment_flg 384 1168
高潮 storm_flg 808 744
地震 eq_flg 775 777
津波 tsunami_flg 1120 432

この表から読み取れること:

表7: 洪水フラグ無 × 浸水域内 ミスマッチ 上位 15 件 (危険スコア降順)

避難所名 市町 標高(m) border_class 土砂 高潮 地震 津波 危険スコア
DCM海田店(駐車場) 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
イトウゴフク海田店 海田町 4.0 B 両規模で水没 1 1 1 1 1.3
フジ海田店 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
エディオン海田店 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
株式会社植田商店 業務用食品スーパー海田店 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
万惣海田店 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
明神公園 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
麒麟倉庫株式会社 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
日の出公園 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
南本町公園 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
東昭和公園 海田町 4.0 B 両規模で水没 1 0 1 0 1.3
大立公園 海田町 4.0 B 両規模で水没 0 0 1 0 1.3
西浜公園 海田町 4.0 B 両規模で水没 1 1 1 0 1.3
荒神保育園 広島市南区 4.0 B 両規模で水没 1 0 0 0 1.3
庚午中三丁目集会所 広島市西区 4.0 B 両規模で水没 1 1 0 0 1.3

この表から読み取れること:

結果 (図と読み取り)

図8: (a) 5 災害種別フラグ × 浸水内 件数 / (b) 洪水フラグ無 × 浸水内の地理分布
図8: (a) 5 災害種別フラグ × 浸水内 件数 / (b) 洪水フラグ無 × 浸水内の地理分布

この図から読み取れること:

分析10: 収容力 × 危険度 バブル散布 (市町別 — 大きいけど危ない避難所の特定)

狙い

これまでの分析は「件数」中心。しかし政策的には 「何人が避難できなくなるか」が本質。 本節では capacity (収容人数) を取り込み、市町別に capacity × danger_score を集計。 2 軸散布図で 「右上象限」 = 大きいけど危ない 市町を特定する。

なぜこの図か (要件H)

方式長所限界判定
(A) 棒グラフ (件数のみ)シンプル容量を反映しない図2 で既出
(B) 散布 (capacity × danger_score) ★2 変数同時 + 回帰直線市町数 = 30 で点が混雑採用
(C) 散布 (避難所 1 件 × danger)4,065 点全部市町判別不能不採用
(D) 棒グラフ (リスク収容人数)1 変数で順位付けcapacity と danger の関係が消える表で代替

手法

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
import matplotlib.pyplot as plt

cap_med = shel_df["capacity"].median(skipna=True)
shel_df["capacity_filled"] = shel_df["capacity"].fillna(cap_med)

cap_summary = shel_df.groupby("muni").agg(
    n_shelters=("facility_id", "count"),
    capacity_total=("capacity_filled", "sum"),
    capacity_mean=("capacity_filled", "mean"),
    danger_score_mean=("danger_score", "mean"),
    n_in_max=("in_max", "sum"),
).reset_index()
cap_summary["pct_in_max"] = cap_summary["n_in_max"] / cap_summary["n_shelters"] * 100
cap_summary["risk_capacity"] = (
    cap_summary["capacity_total"] * cap_summary["danger_score_mean"]
)

# 単回帰
x = cap_summary["capacity_mean"].values
y = cap_summary["danger_score_mean"].values
slope, intercept = np.polyfit(x, y, 1)
r = np.corrcoef(x, y)[0, 1]

結果 (表と読み取り)

表8: リスク収容人数 ランキング 上位 15 (capacity_total × 平均危険スコア)

市町 避難所数 総capacity 平均capacity 平均危険スコア 浸水域内率(%) リスク収容人数
海田町 67 91857.0 1371.000000 0.944288 82.09 86739.5
福山市 474 137210.0 289.472574 0.541851 55.70 74347.4
広島市中区 88 70102.0 796.613636 0.920455 97.73 64525.7
三原市 128 130717.0 1021.226562 0.446875 44.53 58414.2
尾道市 273 317466.0 1162.879121 0.135833 14.29 43122.3
広島市南区 122 53049.0 434.827869 0.728166 80.33 38628.5
広島市西区 136 35840.0 263.529412 0.678686 61.76 24324.1
呉市 518 116979.0 225.828185 0.201022 25.87 23515.4
廿日市市 120 67664.0 563.866667 0.229163 25.00 15506.1
府中町 33 27325.0 828.030303 0.477770 51.52 13055.1
坂町 94 103191.0 1097.776596 0.082982 9.57 8563.0
広島市東区 89 20130.0 226.179775 0.417978 41.57 8413.9
広島市佐伯区 117 32366.0 276.632479 0.201709 43.59 6528.5
府中市 120 229225.0 1910.208333 0.024583 38.33 5635.1
竹原市 71 8034.0 113.154930 0.539903 46.48 4337.6

この表から読み取れること:

結果 (図と読み取り)

図9: 収容力 × 危険度 バブル散布 (市町別) — 右上象限 = 大きいけど危ない
図9: 収容力 × 危険度 バブル散布 (市町別) — 右上象限 = 大きいけど危ない

この図から読み取れること:

分析11: 市町別 危険率ヒートマップ (地理) (危険率 高い市町を一目で)

狙い

分析4 (図2) の市町別棒グラフは 件数 での順位だが、件数大 = 母集団大 のことが多い。 占有率 (危険率 = n_in_max / n_shelters) の方が 地形リスクの本質を映す。 本節では 占有率を地図上の円の色避難所件数を円の半径として、 両指標を 1 図に圧縮する。

なぜこの図か (要件H)

方式長所限界判定
(A) 市町別 危険率 棒グラフ順位明快地理が消える図2 で件数版あり
(B) 地理ヒートマップ ★色=率, 半径=件数, 1 図で 2 指標市町境界が出ない採用
(C) コロプレス (市町ポリゴン色塗)正確な市町境界市町境界 Shapefile が必要不採用 (本記事範囲外)
(D) 散布図 (件数 × 危険率)2 軸明示地理が消える図9 で類似版あり

手法

結果 (表と読み取り)

表9: 市町別 危険率 ランキング 上位 15

市町 避難所数 浸水域内件数 危険率(%) 重心緯度 重心経度
広島市中区 88 86 97.73 34.383787 132.450215
海田町 67 55 82.09 34.369852 132.540938
広島市南区 122 98 80.33 34.369536 132.478354
広島市西区 136 84 61.76 34.391854 132.417310
福山市 474 264 55.70 34.490550 133.350411
府中町 33 17 51.52 34.391401 132.509291
広島市安佐南区 156 77 49.36 34.460156 132.442910
安芸高田市 49 24 48.98 34.684459 132.687028
三次市 84 41 48.81 34.776986 132.886977
竹原市 71 33 46.48 34.349176 132.916192
広島市安芸区 78 36 46.15 34.382988 132.565228
三原市 128 57 44.53 34.439053 133.020808
広島市佐伯区 117 51 43.59 34.405383 132.349149
世羅町 61 26 42.62 34.611769 133.009772
広島市安佐北区 153 64 41.83 34.510114 132.507739

この表から読み取れること:

結果 (図と読み取り)

図10: 市町別 危険率ヒートマップ (地理) — 円の色=危険率 / 円の半径=√避難所件数
図10: 市町別 危険率ヒートマップ (地理) — 円の色=危険率 / 円の半径=√避難所件数

この図から読み取れること:

仮説検証と考察

仮説判定表

仮説 判定 根拠
H1_浸水域内率25-45% 支持 38.2%
H2_沿岸集中>50% 支持 沿岸シェア 61.1% / 危険 1552件中
H3_ボーダー50-500件 反証 1012件
H4_フラグ逆相関 支持 フラグあり 30.9% vs なし 60.6%
H5_全件×危険正の相関 支持 r=0.780

主要発見の物語化

  1. 「避難所自体が水没する」は実証された (H1 支持, 38.2%)。 広島県の避難所 4,065 件のうち 1,552 件 (38.2%) が想定最大規模浸水想定区域内に立地。 これは 3 件に 1 件の頻度で「避難所自体が水没する」ことを意味する。 学校や公民館は住宅地中心に置かれるため、住宅地が浸水する地理を反映した結果。
  2. 沿岸都市集中 (H2 支持, 沿岸シェア 61.1%)。 上位 30 危険避難所のうち過半数が沿岸 5 市町に集中。河川氾濫はデルタ平野・河口部で最も広範囲になるため自然な結果。
  3. ボーダー避難所 = 1012 件 (H3 反証)。 計画規模では安全だが想定最大規模で水没する避難所が 1012 件存在。 これらは 「常時閉鎖」ではなく「警報時切替」という運用設計が必要。 2018 年西日本豪雨級の事象で初めて顕在化するリスク。
  4. 洪水対応フラグの整合性 (H4 支持)。 フラグ有の方が浸水域内率が低く、行政の指定方針は機能している。ただし「フラグ有 × 浸水域内」950 件は運用上の盲点。
  5. 避難所数と危険件数の相関 (H5 r=0.780, 支持)。 避難所が多い市町ほど危険件数も多い = 母集団効果が支配。占有率では市町ランキングが大きく変わるため、件数占有率を併記する必要がある。

方法論的限界 (この記事を「鵜呑みにしない」ためのチェック)

主要発見 (1 段落要約)

広島県の避難所 4,065 件のうち 38.2% (1,552 件) が想定最大規模浸水想定区域内に立地し、 そのうち 1012 件は計画規模では安全な「ボーダー避難所」である。 危険避難所の上位 30 件は標高 30m 以下に集中し、海田町, 広島市西区, 広島市中区 の沿岸都市群に偏在する。 洪水対応フラグ有の浸水域内率は 30.9% で、フラグ無 (60.6%) より低く、指定方針は機能している。 χ² = 279.3 で 統計的に有意。 「避難所自体が水没するか」という反直観的問いに、4,065 件の点 in ポリゴン判定で具体地名レベルの答えが出た。

発展課題 (結果X → 新仮説Y → 課題Z)

本レッスンの結果から、次の 5 つの発展課題が論理的に導かれる:

課題1: 避難所 1 件ごとの実標高で危険スコアを精緻化

課題2: 浸水深カテゴリを取り込んだリスクスコア

課題3: 容量加重リスク (= 何人が避難できなくなるか)

課題4: 高潮浸水・津波浸水との合成リスク

課題5: 危険避難所からの「最近傍安全避難所」距離