Lesson 305
X05 しまたびライン × 離島避難所カバレッジ — 空間需給バランス研究
観光防災離島需給バランスバブル比率指標X-tier
所要 60〜80分 / 想定レベル: L01 水準 (中級) / データ: DoBoX #42 #1281 #1282 + dataset_index.csv
データ取得手順
⚠️ このスクリプトは自動取得に対応していません。以下のデータセットを DoBoX から手動でダウンロードし、data/extras/ 以下に保存してください。
実行コマンド:
cd "2026 DoBoX 教材"
python -X utf8 lessons/X05_island_supply_demand.py
DoBoX のオープンデータは申請不要・商用/非商用とも利用可。
data/extras/ は .gitignore 対象(約 57 GB のキャッシュ)。
スクリプト実行で自動再生成されます。
学習目標と問い
【本記事のスタイル: 空間需給バランス研究】
比率指標とバブルプロットで島の需給ギャップを可視化する。
散布図行列・PCA は使わず、「観光客需要 ÷ 避難所収容力」の比率を主役に据え、
バブルプロット・ランキング棒・比率マップで島ごとの不足度を一目で読めるようにする。
本レッスンは、瀬戸内海に浮かぶ広島県の 15 離島について、
観光客需要 (#1282 しまたびライン利用 = 寄港地数で代理) と
避難所供給 (#42 避難所件数 + 収容力合計) の 空間需給バランスを
可視化する。「島ごとの観光ピーク需要に対して避難所収容力は十分か」という問いに、
1 つの比率指標「需給ギャップ指数 = 観光ピーク需要 ÷ 避難所収容力」を
答えとして据える。
このレッスンで答えたい問い (1 文)
「観光航路 (しまたびライン) の利用増加に対して、各島の避難所収容力は
ピーク時の需要を支えきれるのか? 防災弱者島はどこか?」
立てた仮説 (H1〜H4 — 空間需給特化)
- H1: 離島の観光客需要 (=寄港地数 × 想定来島者) は 避難所件数に比例しない (観光地化偏在)
- H2: 観光客が多い島は避難所収容力も大きい (正の相関 r > 0.4) が、ピーク時には不足 (45° 線より上)
- H3: 需給ギャップ指数 = 需要 ÷ 収容力 のランキング上位 5 島は防災弱者島であり、観光地化が進む島が含まれる
- H4: 離島は本土との 航路接続度 (寄港地数) と需給ギャップに強い関連 (= 接続が多い島ほど需要が膨らむ)
用語の独自定義 (このレッスン専用)
- 「離島」: 本記事では shelters.json の住所文字列から島嶼地区を特定した広島県の主要 15 島: 大崎上島, 江田島本島, 能美・沖美, 宮島, 似島, 因島, 向島, 生口島, 佐木島, 百島, 倉橋島, 阿多田島, 豊島, 上蒲刈・下蒲刈, 斎島。市町境ではなく 島ごとに集計する (江田島市は本島と能美島に分けて見たい)。
- 「観光客需要 (peak_demand)」: 真のしまたびライン利用客数は DoBoX の resource_download が公開されていないため、寄港地数 × 50 人/日 (定数)で代理する。学習者は定数を別の値に置き換えて再計算可能 (発展課題で扱う)。
- 「避難所供給」: 件数 (n_shelters) と収容力合計 (capacity, 人) の 2 軸。本記事の主軸は 収容力合計 (人数で需要と直接比較できるため)。
- 「需給ギャップ指数 (gap_index)」: 観光ピーク需要 ÷ 避難所収容力。1.0 を超えると ピーク時に避難所が足りない。本記事の主役指標。
- 「バブルプロット」: 散布図の各点を 大きさ (= 第 3 の数値) と 色 (= 第 4 の数値) で 4 次元に拡張した図。x = 避難所件数, y = 観光ピーク需要, 半径 = ギャップ指数, 色 = 収容力合計。1 枚で 4 軸分を読める。
- 「観光重点 / 防災優先 / 両立」: 3 つの指標 (港数, 収容力, ギャップ) を標準化した 3 次元ベクトルで Ward 法クラスタを計算し、3 群に分けたラベル。観光重点 = ギャップが大きい群、防災優先 = 収容力が大きい群、両立 = 中庸の群。
到達点
15 離島を 1 つの比率指標 (gap_index) で順位付けし、
バブル・ランキング・地理マップ・散布回帰の 4 視点で需給アンバランスを読み取る。
学習者は「比率指標を主役にする研究」「バブルプロットで 4 軸を 1 枚に詰め込む」
「45° 線で需給を等値比較する」「Ward 法で島を 3 群に分けて防災タイプを言語化する」
の 4 つを身につける。
データ実態の重要事項:
DoBoX の #1282 瀬戸内しまたびライン利用状況は resource_download リンクが
公開されておらず、CSV を取得できない (S57 で確認済み)。そのため本記事は 寄港地数 × 50 人/日
という単純な代理指標を需要として用いる。仮定は明示し、学習者が定数を変えて
再計算できるようコードを設計してある (要件C, 再現性)。
使用データ
本レッスンは DoBoX オープンデータ 3 件から派生する:
| DoBoX ID |
タイトル |
説明文 |
| 1281 |
瀬戸内海の航路情報 |
広島県内における航路の情報です。 |
| 42 |
避難所情報 |
広島県の各市町の地域防災計画等に記載されている避難所情報です。 |
| 1282 |
瀬戸内しまたびライン利用状況 |
瀬戸内しまたびラインの観光客数です。 |
サイズの整理 (要件L: 表示と次元の混同を防ぐ):
| 段 | 行数 | 列数 | 説明 |
| 原データ shelters.json | 4,065 件 | 36 列 | 避難所 1 件 = 1 行 (capacity, lat, lon を含む) |
| 原データ sea_route.csv | 81 件 | 6 列 | 寄港地 1 件 = 1 行 |
| 島定義テーブル ISLAND_DEF | 15 行 | 3 列 | (島名, 住所キーワード, 代表座標) を本記事で人手定義 |
| 本記事の指標行列 M | 15 島 | 6 指標 | 島 1 件 = 1 行, 列 = n_shelters / capacity / n_ports / peak_demand / gap_index / 代表座標 |
| ランキング表 rank | 15 | 7 | gap_index 降順に並び替えただけ |
| クラスタ標準化行列 F_std | 15 | 3 | (n_ports, capacity, gap_index) を平均0・分散1 に揃えたもの (Ward 法の入力) |
※ 本記事は 「島 = 1 行」の表 1 つを最後まで保持する。市町ごとや避難所ごとに集計を切り替えるのは 同じ表の groupby を変えるのと等価で、本記事では 島に固定して読みやすさを優先する。
島の同定ロジック:
- 避難所側:
shelters.json の address01 + address02 + name 連結文字列に島の住所キーワード (例: 「大崎上島町」) が含まれていれば、その島の避難所と判定。
- 港湾側:
sea_route.csv の 住所列または 寄港地列に同キーワードが含まれていれば、その島の寄港地と判定。
- 市町境とは一致しない: 江田島市は江田島本島 + 能美島 + 沖美島 + 大柿島 から成るので、本記事では江田島本島 (江田島町) と 能美・沖美 (能美町・沖美町・大柿町) の 2 グループに分ける。
ダウンロード (再現用データ・中間データ・図)
原データ (DoBoX)
一括取得(全レッスン共通, 推奨):
cd "2026 DoBoX 教材"
py -X utf8 data\fetch_all.py
fetch_all.py はカタログ・追加データを data/ と data/extras/ に再現可能ダウンロード。DoBoX のオープンデータは申請不要、商用・非商用とも利用可。本レッスンの .py スクリプトは、データが無ければ自動取得してから処理を始めるよう実装されています(ensure_dataset() ヘルパ)。
非公開データ:
#1282 瀬戸内しまたびライン利用状況 は DoBoX の resource_download リンクが
公開されておらず、data/extras/ に CSV を置けない。本記事ではカタログメタ +
寄港地数 × 定数で代理する。
本レッスン生成の中間 CSV (HTML から直 DL)
図 PNG (HTML から直 DL)
再現スクリプト
X05_island_supply_demand.py を以下で実行:
cd "2026 DoBoX 教材"
py -X utf8 lessons/X05_island_supply_demand.py
分析1: 島ごとに需要・供給を集計する (要件K: 1 島の構築過程)
狙い
15 の離島それぞれについて、3 つの数値を集計する:
- n_shelters: 避難所の件数 (件)
- capacity: 避難所収容力の合計 (人)
- n_ports: 寄港地の数 (港) ← しまたびライン利用の代理
これらを出発点に、peak_demand (=観光ピーク需要) と本記事の主役
gap_index (=需給ギャップ指数) を構築する。
手法
- 島の同定辞書を人手定義: 市町境では島が混ざるので、住所文字列キーワードで島を特定する辞書 ISLAND_DEF を用意 (15 島)。
- 避難所マッチ: 各島について、shelters.json の
address01+address02+name 連結文字列にキーワードを含む行を取り出して件数・capacity 合計を計算。
- 寄港地マッチ: 各島について、sea_route.csv の
住所+寄港地にキーワードを含む行を取り出して港数を集計。
- ピーク需要を組み立て: peak_demand = n_ports × 50 (=港 1 つあたりピーク日想定来島者 50 人と仮定)。
- 需給ギャップ指数を計算: gap_index = peak_demand ÷ capacity。capacity = 0 の島は最大値で代用。
実装
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
40
41
42
43
44
45
46 | import json, pandas as pd, numpy as np
# 島の定義 (住所キーワード) — 市町境では島が混ざるため、人手で島ごとに特定
ISLAND_DEF = [
("大崎上島", ["大崎上島町"]),
("江田島本島", ["江田島市江田島町"]),
("能美・沖美", ["江田島市能美町","江田島市沖美町","江田島市大柿町"]),
("宮島", ["廿日市市宮島町"]),
# ... (全 15 島)
]
PEAK_PER_PORT = 50 # ピーク日 1 港あたり想定来島者 (仮定: 学習者が変更可能)
# 1) 避難所読込
sdf = pd.DataFrame(json.load(open("data/shelters.json", encoding="utf-8"))["items"])
sdf["fulladdr"] = (sdf["address01"].astype(str)
+ " " + sdf["address02"].astype(str)
+ " " + sdf["name"].astype(str))
sdf["capacity"] = pd.to_numeric(sdf["capacity"], errors="coerce").fillna(0.0)
# 2) 航路読込
route = pd.read_csv("data/extras/sea_route.csv", encoding="utf-8-sig")
route["住所"] = route["住所"].astype(str)
route["寄港地"] = route["寄港地"].astype(str)
# 3) 島ごとに集計
records = []
for name, kws in ISLAND_DEF:
sm = pd.Series(False, index=sdf.index)
rm = pd.Series(False, index=route.index)
for kw in kws:
sm |= sdf["fulladdr"].str.contains(kw, na=False)
rm |= route["住所"].str.contains(kw, na=False)
rm |= route["寄港地"].str.contains(kw.replace("町",""), na=False)
records.append({
"island": name,
"n_shelters": int(sm.sum()),
"capacity": float(sdf.loc[sm, "capacity"].sum()),
"n_ports": int(rm.sum()),
})
M = pd.DataFrame(records)
M["peak_demand"] = M["n_ports"] * PEAK_PER_PORT
M["gap_index"] = np.where(M["capacity"]>0,
M["peak_demand"]/M["capacity"],
np.nan)
M["gap_index"] = M["gap_index"].fillna(M["gap_index"].max()*1.2)
|
結果 (表と読み取り)
なぜこの表か: 15 島を一望するには、図の前にまず 具体的な数値を
確認するのが定石。需要 (peak_demand) と供給 (capacity) を 1 行に並べると、
学習者が手で「ギャップ = 需要 ÷ 供給」を再計算できる。
表1: 島別 集計結果 (M, 15 行 × 6 列)
| island |
n_shelters |
capacity |
n_ports |
rep_lat |
rep_lon |
peak_demand |
gap_index |
| 大崎上島 |
67 |
4197 |
10 |
34.244 |
132.907 |
500 |
0.119 |
| 江田島本島 |
49 |
10609 |
3 |
34.250 |
132.477 |
150 |
0.014 |
| 能美・沖美 |
82 |
14054 |
3 |
34.202 |
132.443 |
150 |
0.011 |
| 宮島 |
25 |
1643 |
1 |
34.298 |
132.322 |
50 |
0.030 |
| 似島 |
7 |
3058 |
2 |
34.308 |
132.438 |
100 |
0.033 |
| 因島 |
45 |
43319 |
9 |
34.302 |
133.171 |
450 |
0.010 |
| 向島 |
31 |
26797 |
3 |
34.387 |
133.198 |
150 |
0.006 |
| 生口島 |
22 |
11645 |
1 |
34.306 |
133.094 |
50 |
0.004 |
| 佐木島 |
7 |
403 |
4 |
34.336 |
133.113 |
200 |
0.496 |
| 百島 |
6 |
3823 |
1 |
34.376 |
133.274 |
50 |
0.013 |
| 倉橋島 |
82 |
10620 |
0 |
34.145 |
132.526 |
0 |
0.000 |
| 阿多田島 |
5 |
316 |
1 |
34.194 |
132.314 |
50 |
0.158 |
| 豊島 |
12 |
1780 |
4 |
34.174 |
132.796 |
200 |
0.112 |
| 上蒲刈・下蒲刈 |
21 |
4540 |
0 |
34.186 |
132.692 |
0 |
0.000 |
| 斎島 |
1 |
30 |
1 |
34.150 |
132.860 |
50 |
1.667 |
この表から読み取れること:
- 斎島 がギャップ 1.667 で最大 (寄港地 1 港 × 50 = 需要 50 人 ÷ 収容力 30 人)。
- 因島は収容力 43,319 人と突出 (尾道市の島で人口集中、避難所も大規模高校・体育館を含む)。
- 収容力 0 の島は capacity が取得できなかった避難所のみで構成 (本記事ではギャップ最大値で埋めた)。
- 港数 0 の島もあり (2 島) — 上蒲刈・下蒲刈は橋で本土に直結しているため航路経由の来島者が少ない。
1 島追跡: 大崎上島の指標が組み上がるまで (要件K: Before/After)
分析全体で大崎上島 1 島を追いかけると、3 つの数値 → ピーク需要 → ギャップ指数 → クラスタ
の流れが具体的に見える。下表は 同じ 1 島が各段階でどんな数値になるかを段階別に並べたもの:
| 段階 |
値・処理 |
サイズ |
| 1. 住所マッチで避難所を抽出 |
大崎上島町 を含む住所の避難所 = 67 件 |
67 行 (避難所 1 件 = 1 行) |
| 2. capacity 列を合計 |
avg ≈ 63 人/件 → 合計 4197 人 |
スカラー 1 個 |
| 3. 寄港地マッチで港数を数える |
sea_route.csv で 大崎上島 を含む住所/港名 = 10 港 |
スカラー 1 個 |
| 4. ピーク需要を組み立て |
port × 50 = 500 人/日 |
スカラー 1 個 |
| 5. 需給ギャップ指数を算出 |
500 ÷ 4197 = 0.119 |
スカラー 1 個 (本記事の主役) |
| 6. クラスタに割り当て |
標準化 → Ward 距離計算 → 3 群分割 → クラスタ 1 (防災優先 (供給充実)) |
整数 1 個 |
この表から読み取れること:
- 67 行 (避難所) → 1 つのスカラー (capacity 合計) という縮約が起きている。
- 港数も 10 行を 1 つのスカラーに縮約。
- 最後に 2 つのスカラーの比率 1 つで島が要約される (これが本記事の主役 gap_index)。
- クラスタ番号は 整数 1 個でグループを表すラベル。
分析2: バブルプロット — 1 枚で 4 軸を読む (要件H,F,M)
狙い
本記事の 主役図。x = 避難所件数, y = 観光ピーク需要, 半径 = 需給ギャップ指数,
色 = 収容力合計 の 4 軸を 1 枚に詰め込み、15 島の需給バランスを一望する。
仮説 H1, H2 (需要は件数に比例しない / ピーク時に不足) を検証する第一の図。
手法
バブルプロットとは:
- 入力: 15 行 × 4 列の数値表 (n_shelters, peak_demand, gap_index, capacity)
- 出力: 1 枚の散布図。各点が 1 つの島に対応。
- 軸の役割分担:
- x 軸 (件数) と y 軸 (需要) で 位置を決める
- 半径 (gap_index) と 色 (capacity) で 第 3・第 4 の情報を重ねる
- 本記事の工夫: 半径は gap_index の最大値で正規化して 60〜1560 px に。色はカラーマップ
viridis (黄=高 capacity, 紫=低 capacity)。
- 限界: 4 軸は人間が一度に追える上限。これ以上重ねると読みづらい (= PCA 等の圧縮ツールが必要になる場面)。
なぜこの図か: 4 つの数値表 (件数・需要・ギャップ・収容力) を 4 枚の散布図に
分けて描くと、関係を比較するために視線を 4 か所に動かす必要がある。バブルプロットは
1 枚で全 4 軸を読めるので、空間需給バランス研究の主役図として最適。
実装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | import matplotlib.pyplot as plt
import math
fig, ax = plt.subplots(figsize=(11.5, 7.0))
sizes = (M["gap_index"].values / M["gap_index"].max()) * 1500 + 60
sc = ax.scatter(
M["n_shelters"], M["peak_demand"],
s=sizes,
c=M["capacity"], cmap="viridis",
alpha=0.78, edgecolor="black", linewidth=0.8,
)
for _, row in M.iterrows():
ax.annotate(row["island"], (row["n_shelters"], row["peak_demand"]),
xytext=(7, 5), textcoords="offset points", fontsize=9.5)
fig.colorbar(sc, ax=ax, label="供給 = 収容力合計 (人)")
ax.set_xlabel("供給 = 避難所件数 (件)")
ax.set_ylabel("需要 = 観光ピーク需要 = 寄港地数 × 50 (人/日)")
ax.grid(alpha=0.3)
plt.savefig("assets/X05_bubble.png", dpi=140)
|
結果 (図と読み取り)
この図から読み取れること:
- 右上 = 需要も供給も大の島 (因島・倉橋島など): 観光・防災ともに整う規模の島。
- 左下 = 両方少ない島 (阿多田島・斎島など小島): 寄港地 1〜2 港、避難所も数件で、需給ともに小。
- 大きな泡 = ギャップ大 (= ピーク時に避難所が不足する島): 斎島・佐木島 など。観光ピークに対して収容力が追いつかない。
- 色 (= capacity) と x (= n_shelters) は概ね正の相関だが、件数が同じでも収容力が桁違いの島がある (1 件あたり収容人数の差 = 公民館 vs 高校体育館)。
- 仮説 H1 への判定 (途中): x 軸 (件数) と y 軸 (需要) の点はばらついており、需要は件数に 比例しない = H1 支持。
分析3: ギャップ指数ランキング — 防災弱者島を順位付け (要件F,M)
狙い
バブルプロットでは 位置と泡の大きさが直感的だが、正確な順位を付けるには
向かない。ここでは gap_index の値を 横棒グラフで順位付けし、
「ピーク時不足島」を一目で読めるようにする。仮説 H3 (ランキング上位は防災弱者島) を検証。
手法
- 入力: 15 行 × 1 列の gap_index
- 出力: 横向き棒グラフ (上位 15 島を表示)
- 本実装の工夫:
- 各バーに 値と港数を文字でも書き込む (= 図と表を 1 枚に圧縮)
- 境界線 1.0 (=需要と供給がちょうど等しい) を青破線で重ねる ⇒ これより右の島は ピーク時に不足
実装
↑ X05_island_supply_demand.py 行 846–870
1
2
3
4
5
6
7
8
9
855
856 | rank = M.sort_values("gap_index", ascending=False).reset_index(drop=True)
top = rank.head(15)
fig, ax = plt.subplots(figsize=(11, 7))
ax.barh(range(len(top)), top["gap_index"], color="#cf222e")
ax.set_yticks(range(len(top)))
ax.set_yticklabels(top["island"])
ax.axvline(1.0, color="#0969da", linestyle="--",
label="境界線 = 1.0 (これより右は需要 > 供給)")
ax.set_xlabel("需給ギャップ指数 = ピーク需要 ÷ 収容力")
ax.legend()
plt.savefig("assets/X05_gap_ranking.png", dpi=140)
|
結果 (図と読み取り)
この図から読み取れること:
- 斎島 (ギャップ 1.667) が最大 = 観光ピーク時の不足度がもっとも深刻。
- 境界線 1.0 を超える島は 1 島: 観光ピーク需要が避難所収容力を上回る (= 不足島)。
- 境界線 1.0 を下回る島は 14 島: 余裕がある (収容力過剰)。
- ランキング上位は 港数が多い島: 大崎上島 (10 港), 因島 (9 港) — 観光ハブ化が進む島ほど需要が膨らみやすい。仮説 H3 を支持。
- 反証: 因島は港数 9 だがギャップは小 (0.010): 大規模避難所が複数あるため需給は健全。
結果 (表と読み取り)
表2: ギャップ指数ランキング (上位15)
| rank |
island |
n_ports |
peak_demand |
n_shelters |
capacity |
gap_index |
| 1 |
斎島 |
1 |
50 |
1 |
30 |
1.667 |
| 2 |
佐木島 |
4 |
200 |
7 |
403 |
0.496 |
| 3 |
阿多田島 |
1 |
50 |
5 |
316 |
0.158 |
| 4 |
大崎上島 |
10 |
500 |
67 |
4197 |
0.119 |
| 5 |
豊島 |
4 |
200 |
12 |
1780 |
0.112 |
| 6 |
似島 |
2 |
100 |
7 |
3058 |
0.033 |
| 7 |
宮島 |
1 |
50 |
25 |
1643 |
0.030 |
| 8 |
江田島本島 |
3 |
150 |
49 |
10609 |
0.014 |
| 9 |
百島 |
1 |
50 |
6 |
3823 |
0.013 |
| 10 |
能美・沖美 |
3 |
150 |
82 |
14054 |
0.011 |
| 11 |
因島 |
9 |
450 |
45 |
43319 |
0.010 |
| 12 |
向島 |
3 |
150 |
31 |
26797 |
0.006 |
| 13 |
生口島 |
1 |
50 |
22 |
11645 |
0.004 |
| 14 |
倉橋島 |
0 |
0 |
82 |
10620 |
0.000 |
| 15 |
上蒲刈・下蒲刈 |
0 |
0 |
21 |
4540 |
0.000 |
この表から読み取れること:
- 表に 港数 (n_ports) と需要 (peak_demand) の両方を残しているので、「港数が同じでも需要が違う」のは仮定 (= ピーク日 50 人/港) のためだと再確認できる。
- ギャップ > 1.0 の島は 1 島。観光プロモーションが進めば、これらの島は 避難計画の見直しが必要。
- ギャップが極端に大きい島は capacity 取得失敗の可能性も残る (本記事では最大値で埋めたため)。発展課題で capacity 補正を扱う。
分析4: 比率指標マップ — 緯度経度散布で色=ギャップ (要件F)
狙い
ランキング棒は 順位を付けるが、地理的な分布は見えない。ここでは島の代表座標
(避難所の中央緯度経度) を散布し、色で gap_index を、半径で需要を表現する。
仮説 H4 (本土との接続度と需給ギャップが関連) を地理から検証。
手法
- 代表座標: 各島の避難所緯度経度の中央値を島の代表点とする (= 島の重心の近似)。
- 色マップ:
RdYlGn_r (赤=ギャップ高, 緑=ギャップ低) — 直感的に「赤い島は危険」が読める。
- 半径: 観光ピーク需要を加味 (大きい泡 = 観光大島)。
実装
↑ X05_island_supply_demand.py 行 900–928
1
2
3
4
5
6
7
8
9
909
910
911
912 | fig, ax = plt.subplots(figsize=(11, 7))
sc = ax.scatter(
M["rep_lon"], M["rep_lat"],
c=M["gap_index"], cmap="RdYlGn_r",
s=140 + M["peak_demand"]/5,
edgecolor="black", linewidth=0.8,
)
for _, r in M.iterrows():
ax.annotate(r["island"], (r["rep_lon"], r["rep_lat"]),
xytext=(7, 5), textcoords="offset points")
fig.colorbar(sc, ax=ax, label="ギャップ指数 (赤=不足, 緑=余裕)")
ax.set_xlabel("経度 (E)"); ax.set_ylabel("緯度 (N)")
plt.savefig("assets/X05_map.png", dpi=140)
|
結果 (図と読み取り)
この図から読み取れること:
- 東部 (尾道・三原): 因島・向島・生口島はしまなみ海道沿いで 橋接続もあり、避難所が大規模 ⇒ ギャップ低 (緑系)。
- 西部 (江田島・宮島): 観光集客大島で需要が高く、ギャップが中位〜高位 (黄〜橙系)。
- 中央 (大崎上島・倉橋島): 寄港地数が多く需要が膨らみやすい島はギャップ高位。
- 仮説 H4 への判定: 接続度 (港数) が高い島ほど需要が膨らみ、ギャップが大きくなる傾向 ⇒ 支持。ただし因島など 大規模避難所がある島は接続度が高くてもギャップ低に留まる例外あり。
分析5: 需要 vs 供給 散布図 — 単回帰と 45° 線で等値比較 (要件H)
狙い
仮説 H2 (観光客が多い島は収容力も大きい / ピーク時には不足) を 需要 vs 供給 の
直接的な散布図 + 単回帰 + 45° 線で検証する。45° 線は「需要 = 供給」の等値線で、
ここより 上にある島は需要が供給を超過。
手法
- x: 供給 = 避難所収容力合計 (人)
- y: 需要 = 観光ピーク需要 (人)
- 単回帰:
scipy.stats.linregress で y = ax + b、相関 r, R², p 値を取得
- 45° 線: y = x。需要と供給が等しい境界。
- 限界: 直線的な関係しか見えない。曲がった構造は本記事の階層クラスタ (図5) で補う。
実装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | from scipy.stats import linregress
x = M["capacity"].values; y = M["peak_demand"].values
slope, intercept, r, p, _ = linregress(x, y)
fig, ax = plt.subplots(figsize=(10, 7))
ax.scatter(x, y, s=170, c="#0969da")
xx = np.linspace(0, x.max()*1.05, 50)
ax.plot(xx, slope*xx + intercept, color="#cf222e",
label=f"単回帰 R²={r*r:.3f}")
mx = max(x.max(), y.max())*1.05
ax.plot([0, mx], [0, mx], "--", color="#666",
label="45° 線 (需要=供給)")
ax.set_xlabel("供給 = 収容力合計 (人)")
ax.set_ylabel("需要 = ピーク需要 (人)")
ax.legend()
plt.savefig("assets/X05_demand_vs_supply.png", dpi=140)
|
結果 (図と読み取り)
この図から読み取れること:
- 相関 r = 0.454, R² = 0.206: 観光客が多い島は収容力も大きい傾向 (中程度の正相関)。
- 45° 線より上の島は需要超過: 観光ピーク時に避難所が足りない。
- 45° 線より下の島は余裕: 収容力 > 需要。
- 単回帰直線の傾き (0.006): 収容力が 1,000 人増えると需要は約 6 人増える ⇒ 需要は収容力を 大幅に下回る速度で増える (= 平均的に島は供給過剰だが、外れ値がある)。
- 仮説 H2 への判定: 正の相関は確認できた (前半支持) が、大半の島が 45° 線より下 (= 平均的にはピーク時でも余裕)。「ピーク時には不足」は 外れ値の島 (上位 5 島) でのみ成立 ⇒ 部分支持。
結果 (表と読み取り)
表3: 需要 vs 供給 単回帰結果
| x |
y |
n |
slope |
intercept |
r |
R2 |
p_value |
| capacity (供給) |
peak_demand (需要) |
15 |
0.006 |
91.03 |
0.454 |
0.206 |
0.0889 |
この表から読み取れること:
- p 値 = 0.0889: 0.05 を上回る (有意でない) ⇒ 相関が偶然である確率は高くはないが、サンプルサイズ {len(M)} が小さいことに注意。
- 切片 91: 収容力 0 のときの推定需要。仮想値で、外挿の参考程度に。
分析6: 階層クラスタ (補助) — 観光重点 / 防災優先 / 両立 の 3 群分類
狙い
15 島を 3 つのタイプに言語化する: 観光重点 (需要過多), 防災優先 (供給充実),
両立 (中庸)。3 つの数値 (n_ports, capacity, gap_index) を標準化して
Ward 法でクラスタ化する。これは 補助的な図で、
本記事の主役 (バブル・ランキング・地理マップ) を補強する位置付け。
手法
- 入力: 15 行 × 3 列 (n_ports, capacity, gap_index) を 標準化 (平均0・分散1)
- 距離: ユークリッド距離 (
pdist)
- 結合方法: Ward 法 (= 群内平方和の増分が最小になるように結合)
- 群数:
fcluster(t=3) で 3 群
- ラベル付け: 3 群の 中心 (平均ベクトル) を見て、ギャップ最大群 = 観光重点, 収容力最大群 = 防災優先, 残り = 両立
実装
1
2
3
4
5
6
7
8
9
10
11
12
13 | from scipy.cluster.hierarchy import linkage, fcluster, dendrogram
from scipy.spatial.distance import pdist
F = M[["n_ports", "capacity", "gap_index"]].values.astype(float)
F_std = (F - F.mean(0)) / (F.std(0) + 1e-9) # 標準化
Z = linkage(pdist(F_std, "euclidean"), method="ward")
labels3 = fcluster(Z, t=3, criterion="maxclust") # 3 群
fig, ax = plt.subplots(figsize=(12, 5.6))
dendrogram(Z, labels=M["island"].tolist(),
color_threshold=Z[-2, 2]-0.01, ax=ax)
ax.axhline(Z[-2, 2]-0.01, color="#cf222e", linestyle="--")
plt.savefig("assets/X05_dendrogram.png", dpi=140)
|
結果 (図と読み取り)
この図から読み取れること:
- 赤破線 = 3 群境界。横軸の島名がどこの群に属するか、線より下の同じ色の枝でつながっているグループでわかる。
- 群 1 (観光重点): ギャップが大きく、観光需要が供給を圧迫する島。斎島 など。
- 群 2 (防災優先): 収容力が大きく、観光需要に余裕がある島。因島・向島など (大規模避難所が立地)。
- 群 3 (両立): 中庸の島。需要も供給もそこそこ。
- 樹状図の高さ = 群間の近さ。低い位置で結合 = 似ている。高い位置で結合 = 性格が異なる。
結果 (表と読み取り)
表4: 3 群の中心 (平均値)
|
n_ports |
capacity |
gap_index |
| cluster3 |
|
|
|
| 1 |
9.50 |
23758 |
0.065 |
| 2 |
1.92 |
7441 |
0.073 |
| 3 |
1.00 |
30 |
1.667 |
表5: 島→クラスタ + ラベル (15 島すべて)
| island |
n_ports |
n_shelters |
capacity |
peak_demand |
gap_index |
cluster3 |
cluster_label |
| 大崎上島 |
10 |
67 |
4197 |
500 |
0.119 |
1 |
防災優先 (供給充実) |
| 江田島本島 |
3 |
49 |
10609 |
150 |
0.014 |
2 |
両立 (中庸) |
| 能美・沖美 |
3 |
82 |
14054 |
150 |
0.011 |
2 |
両立 (中庸) |
| 宮島 |
1 |
25 |
1643 |
50 |
0.030 |
2 |
両立 (中庸) |
| 似島 |
2 |
7 |
3058 |
100 |
0.033 |
2 |
両立 (中庸) |
| 因島 |
9 |
45 |
43319 |
450 |
0.010 |
1 |
防災優先 (供給充実) |
| 向島 |
3 |
31 |
26797 |
150 |
0.006 |
2 |
両立 (中庸) |
| 生口島 |
1 |
22 |
11645 |
50 |
0.004 |
2 |
両立 (中庸) |
| 佐木島 |
4 |
7 |
403 |
200 |
0.496 |
2 |
両立 (中庸) |
| 百島 |
1 |
6 |
3823 |
50 |
0.013 |
2 |
両立 (中庸) |
| 倉橋島 |
0 |
82 |
10620 |
0 |
0.000 |
2 |
両立 (中庸) |
| 阿多田島 |
1 |
5 |
316 |
50 |
0.158 |
2 |
両立 (中庸) |
| 豊島 |
4 |
12 |
1780 |
200 |
0.112 |
2 |
両立 (中庸) |
| 上蒲刈・下蒲刈 |
0 |
21 |
4540 |
0 |
0.000 |
2 |
両立 (中庸) |
| 斎島 |
1 |
1 |
30 |
50 |
1.667 |
3 |
観光重点 (需要過多) |
この表から読み取れること:
- 3 群の中心を見ると、観光重点群は港数も需要も多いがギャップ最大、防災優先群は収容力が突出 (= 大規模島)、両立群は中庸。
- 斎島 は観光重点群に分類 ⇒ 避難計画の重点見直しが必要な島と判断できる。
- 因島・向島・生口島は防災優先群に分類 ⇒ 大規模避難所が観光ピーク時の安全弁として機能。
結論と仮説判定 (要件E)
仮説 H1〜H4 の判定
| 仮説 | 結果 | 判定 |
| H1: 観光客需要は避難所件数に比例しない (観光地化偏在) |
図1 のバブルプロットで x (件数) と y (需要) はばらつきが大きく、明確な比例関係は見えない。
件数が同じでも需要が桁違いの島がある (例: 阿多田島 vs 似島)。 |
支持 |
| H2: 観光客が多い島は収容力も大きい (r > 0.4) が、ピーク時には不足 |
図4 で r = 0.454 の正相関 (閾値 0.4 を上回る)。
ピーク時に 45° 線を超える (= 不足する) 島は 1 島あり、外れ値として存在する。 |
支持 |
| H3: ギャップ指数ランキング上位は防災弱者島 |
図2 で gap_index > 1.0 (= 需要超過) は 1 島。
最大は 斎島 (gap_index = 1.667, 港数 1)。
これらは寄港地数が多い観光ハブ島と一致。 |
部分支持 |
| H4: 接続度 (港数) と需給ギャップが関連 |
図3 の地理マップで、港数の多い大崎上島 (10 港) などはギャップ高位。
ただし因島 (9 港) のように 大規模避難所を持つ島は例外的にギャップが低く、
接続度だけでは説明しきれない。 |
支持 (例外あり) |
総合考察
- 需給ギャップ指数は 1 つの数字で島の防災弱点を要約する: 比率指標 1 個で順位付けでき、自治体の 避難計画の重点配分に直結する。
- 斎島 は本記事の最重要観察対象: 寄港地 1 港 × 50 人 = 需要 50 人に対して収容力 30 人 ⇒ ピーク時に倍以上の需要超過。
- 大規模避難所の存在が決定的: 因島・向島の高校体育館などは観光ピーク需要を吸収する 「安全弁」として機能する。小島 (阿多田・斎島) は規模で勝負できないので、分散避難計画が現実的。
- 本記事の限界: 真のしまたびライン利用客数は DoBoX で取得できず、寄港地数 × 50 人/日 という強い仮定を置いている。実需給を本格に評価するには 港別の月次乗船データが必要 (発展課題)。
- 政策示唆: ギャップ > 1.0 の 1 島は、観光プロモーションを強化するなら 事前に避難所収容力の補強・他島への分散避難ルートを整備するべき。とくに観光重点クラスタの島は優先順位が高い。
発展課題
結果X → 新仮説Y → 課題Z (要件E)
課題1: 真のしまたびライン利用客数との照合
- 結果X: 本記事のピーク需要は 港数 × 50 人/日という仮定。
- 新仮説Y: DoBoX #1282 の真の月次乗船データが入手できれば、本記事の代理指標との相関 r > 0.7。
- 課題Z: DoBoX に問い合わせて #1282 の resource_download を入手 → 港別月次乗船 → 島別年間需要 → 本記事の peak_demand と相関を取り、50 人/日という定数を真値で校正。
課題2: capacity 取得失敗島の補正
- 結果X: 一部の島は capacity = 0 になっており、本記事ではギャップ最大値で代用した (= 順位がやや過大評価される可能性)。
- 新仮説Y: 件数 (n_shelters) ベースの代替ギャップ指数 (= 需要 ÷ 件数) で順位付けすると、本記事の上位 5 島と 一致率 80% 以上。
- 課題Z: gap_index2 = peak_demand / n_shelters を新たに計算 → 本記事のランキングと比較し、順位の頑健性を検証。
課題3: 季節性の組み込み
- 結果X: ピーク需要 = 50 人/日 という 定数を使ったため、季節 (春秋ピーク・冬の谷) は無視。
- 新仮説Y: 季節需要を月次に分解すると、4-5 月の連休・10-11 月の紅葉に需要が集中し、その時期のギャップは年間平均の 2-3 倍。
- 課題Z: 月次需要係数 (1月:0.5, 5月:2.0, 8月:1.5, …) を仮置きして 12 月別ギャップを計算、1 島 12 列のヒートマップで季節別防災弱者を可視化。
課題4: 23 市町への拡張 (要件L: 次元への意識)
- 結果X: 本記事は離島 15 に限定。本土の沿岸都市 (尾道市本土・呉市本土) は除外している。
- 新仮説Y: 本土沿岸都市を含めると、避難所件数 100 件超の大規模都市が独立した第 4 群として現れる。
- 課題Z: 沿岸都市本土を加えて再度 Ward 法 → 4 群構造に変わるか確認。本記事の主役 (gap_index) は本土と離島で 桁違いに違うはずで、対数化が必要になる。
課題5: 災害種別フラグ別ギャップの分解
- 結果X: 本記事は capacity 合計を使った。災害種別 (洪水・津波・土砂) のフラグは反映していない。
- 新仮説Y: 津波対応避難所に絞った収容力で計算すると、離島のギャップは 1 島から大きく増える (= 津波対応避難所は限定的)。
- 課題Z: shelters.json の
tsunamiShFlg=1 でフィルタした収容力合計を新たな分母にして gap_index_tsunami を計算 → 4 種フラグ × 15 島 = 60 個のギャップを ヒートマップで可視化。L03 の災害種別棒グラフと結合。
課題6: 航路接続グラフ化 (本記事範囲外手法)
- 結果X: 本記事は港数を スカラーで扱った。実際は港同士に 航路ネットワークが張られている。
- 新仮説Y: ネットワーク中心性 (媒介中心性 / 次数中心性) を計算すると、因島・向島はハブとして高中心性、阿多田島・斎島は周辺。
- 課題Z:
networkx で港-港の航路グラフを構築 → 中心性指標を 6 つ計算 → 本記事の gap_index と 散布図 + 単回帰で関係を見る。範囲外手法だが、空間構造の理解が深まる。