Lesson 01

X01: インフラツーリズム × 防災施設の二重利用 — 観光対象になる/ならないは規模・年代・立地から判別可能か

X系横断研究防災インフラ観光ロジスティック回帰PCA重回帰リテラシ
所要 90分 / 想定レベル: リテラシ基礎〜発展 / データ: 4 CSV を施設名でマージ: DoBoX #1278 インフラツーリズム (40件), #1185 ダム (12基), #1184 橋梁 (4,203基), #1186 トンネル (157本)
【本記事のスタイル: 分類モデル研究】
ロジスティック回帰で「観光対象になる/ならない」をデータから予測。AUCで予測性能を評価。係数解釈で「何が観光対象を決めているか」を読む。

データ取得手順

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

IDデータセット名
#888都市計画区域情報_区域データ_安芸高田市_行政区域
#1184dataset #1184
#1185dataset #1185
#1186dataset #1186
#1278過去に発生した災害情報

実行コマンド:

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

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

学習目標と問い

このレッスンで答えたい問い

「広島県の防災インフラ(ダム12基/橋梁4,203基/トンネル157本)のうち、 どれが『観光対象』として外部公開されているか? そして、観光対象になる施設は規模・年代・立地から 事前に予測できるのか?」

用語の定義(このレッスン独自)

  • 「インフラツーリズム」: ダム・橋梁・砂防施設など 本来は防災・交通用途のインフラ施設を観光資源として公開する取り組み。 広島県は DoBoX #1278「インフラツーリズム」 でカタログ化(本記事では 40件)。
  • 「観光対象施設」: 上記カタログ40件に名前が登録されている施設。本記事では「観光対象=1」、それ以外を「観光対象=0」 として2値ラベルにする。
  • 「観光フラグ」: ダム/橋梁/トンネルの台帳に対して、施設名で照合し「カタログに載っているか」を 0/1 で立てた列。本記事独自の派生列であり、DoBoX 公式列ではない。
  • 「ロジスティック回帰」: 「YESかNOか」を確率で答える機械学習ツール。 複数の特徴(延長・幅員・築年)を入れると、観光対象になる確率 (0〜1) を返す。中身の数式は黒箱でOK。係数を見れば「どの特徴が効いたか」も分かる。
  • 「LDA(線形判別分析)」: ロジスティック回帰と 近い目的(YES/NO 判別)のツール。2グループの平均が最も離れる軸を探して投影する。本記事では PCA で代用するが、参考として併記。
  • 「PCA(主成分分析)」: 多くの列を 2〜3 列に圧縮するツール。「全 4 列を 1 枚の散布図にしたい」が目的。圧縮後の軸(PC1, PC2)に観光フラグを重ねると、観光対象が空間のどこに集まるか見える。
  • 「重回帰」: 1 つの結果を、複数の原因で説明するツール。 本記事では「市町の観光対象数 = a×ダム数 + b×橋梁数 + c×トンネル数」と立てる。
  • 「AUC」: ロジスティック回帰の 「弁別力」点数(0.5 = 偶然, 1.0 = 完全)。 正例と負例をランダムに 1 件ずつ取って、正例の予測確率の方が高い割合

立てた仮説

  1. H1(観光対象ダムは大規模偏重): 12基のダムのうち観光対象ダムは、非観光ダムより 容量・堤高が中央値で 1.5 倍以上大きいはず。 理由: 大規模ダムは見学価値(迫力)と既存の見学路の整備が揃っている。
  2. H2(観光対象橋梁は P75 以上に偏在): 4,203 橋梁のうち観光対象は 延長 P75 以上の上位橋梁に強く偏るはず。 小規模橋梁は観光資源として宣伝されない。
  3. H3(観光対象は道路アクセス性が高い): 観光対象橋梁は 国道・主要県道沿いに集中するはず。 車でアクセスできない橋梁は観光ルート化されない。
  4. H4(観光対象は規模・年代・道路種別から判別可能): ロジスティック回帰で「観光対象になる/ならない」を 橋梁の延長・幅員・築年・国道フラグから予測すると、 AUC ≥ 0.85 で弁別できるはず。 最も効く特徴は「log_延長」(規模が観光対象化の最大の駆動因)。
  5. H5(市町の観光対象数 ∝ インフラ整備度): 重回帰「観光対象数 = a×ダム数 + b×橋梁数 + c×トンネル数」を市町別に組むと、 R² ≥ 0.5 で説明できるはず。 ダム1基あたりが橋梁1基より観光対象化されやすい(係数 a ≫ b)。

到達点

使用データ

本レッスンは 4 つの実データ CSV を施設名でマージする空間結合の典型例。 ダム/橋梁/トンネル の台帳には観光フラグが もともと無いため、 インフラツーリズム カタログ(40件)から自分で派生させる

サイズ感の混同に注意(要件L): インフラツーリズム カタログは 40件観光対象施設リスト。 そこから「ダム」「橋」「トンネル」の名前を抜き出すと、それぞれ ダム 12, 橋梁 18, トンネル 1 の名前候補が得られる。 これら名前候補を、4,203 橋梁・157 トンネル の全件台帳に対して照合するため、 照合後のマッチ率は橋梁で約 56%(10/18, 残り8件は国道・有料道路など県外管理)。 台帳数(4,203)と観光フラグ正例数(10)を 10 桁違うサイズの集合の合流 として扱う。

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

本レッスンの 全成果物に直リンクを置いた。 HTML を読むだけで生データ・中間 CSV・図 PNG・Python ソース が全て手元に揃う。

1. 生データ(DoBoX 由来)

ファイル形式件数取得元
data/extras/infra_tourism.csv CSV40 件 DoBoX #1278
data/extras/dam_basic.csv CSV12 基 DoBoX #1185
data/extras/bridge_basic.csv CSV4,203 基 DoBoX #1184
data/extras/tunnel_basic.csv CSV157 本 DoBoX #1186

2. プログラムで生成される中間データ(CSV 直リンク)

ファイル内容使う分析
X01_dam_with_flag.csv 12 ダム × (規模・観光対象0/1)分析1 (H1)
X01_bridge_sample_with_flag.csv 橋梁観光対象10件+非対象300件サンプル分析2-4 (H2-H4)
X01_tunnel_with_flag.csv 157 トンネル × 観光フラグ分析2 補助
X01_h1_dam_summary.csv ダム規模指標 4種の中央値比較分析1
X01_h2_bridge_summary.csv 橋梁延長の P75/P90/P99 統計分析2
X01_h3_roadtype.csv 道路種別比率 (観光 vs 非観光)分析3
X01_h4_lr_coef.csv ロジスティック回帰 4特徴の係数とオッズ比分析4
X01_h4_pca_loadings.csv PCA の特徴 → PC1/PC2 ローディング分析5
X01_h4_top50_pred.csv ロジ回帰で観光対象確率 上位 50 橋梁分析4
X01_h5_ols_coef.csv 市町別 重回帰の係数・p値・95%CI分析6
X01_h5_city_table.csv 22 市町 × 6 列 + 観光対象数 + 予測値分析6
X01_combined_with_flag.csv ダム+橋梁+トンネル統合 (規模・経過年・観光フラグ)分析7

3. 図 PNG(直リンク)

4. 再現スクリプト

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

スクリプト本体: lessons/X01_infra_tourism_dual_use.py

分析1: 4 CSV を施設名でマージし「観光フラグ」列を派生する

狙い

4 つの実データ CSV には 共通 ID が無い。 インフラツーリズム カタログの「施設名称」と、各台帳の「ダム名/施設名」を 文字列マッチで結合し、 台帳側に 観光対象=0/1 のフラグ列を派生する。これが以降すべての分析の 共通入力になる。

使う道具: pandas merge / isin

役割(一文で): 2 つの DataFrame を「キー列の値が同じ行同士」で結合する。 本レッスンでは merge よりも 「カタログ側の名前セットに含まれるか」を isin で問う方が単純

ステップ操作入力出力
1名前を正規化(【路線】や(旧名)を除去)「早瀬大橋【国道487号】」「早瀬大橋」
2カタログを 3 つの名前セットに分割(ダム/橋/トンネル)40件のカタログ名前 set 3個
3isin(名前set) で 0/1 フラグ列を作る4,203 橋梁の名前列4,203 行の 0/1 フラグ

注意点 3つ

1施設の Before/After — 「広島空港大橋」を最後まで追う表(要件K)

段階このデータで何が起きるかサイズ
① カタログ取得infra.csv の "広島空港大橋【本郷大和線】" を読む1行
② 名前正規化cleanname() で「広島空港大橋」に統一1スカラー
③ カテゴリ判別名前に「橋」を含む → infra_bridge_names セットに入るset への 1件追加
④ 橋梁台帳をスキャン4,203 橋梁の cleanname(施設名) をすべて作る4,203 スカラー
isin各橋梁名が infra_bridge_names に入るか 0/1 で答える4,203 行のフラグ
⑥ 結果「広島空港大橋」(橋梁番号: 070300006) は 観光対象=1 に立つ1セルの値

実装

狙い: 4 つの CSV を読み込み、施設名を正規化して、3 つの名前セットに振り分け、各台帳に 0/1 フラグ列を立てる。 重要行は ① 正規表現で表記ゆれを吸収する cleannameisin(セット)

 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
import re, pandas as pd

# 1. 4 つの CSV を読む
infra  = pd.read_csv("data/extras/infra_tourism.csv", encoding="utf-8-sig")
dam    = pd.read_csv("data/extras/dam_basic.csv",   encoding="utf-8-sig").dropna(subset=["ダム名"])
bridge = pd.read_csv("data/extras/bridge_basic.csv", encoding="utf-8-sig")
tunnel = pd.read_csv("data/extras/tunnel_basic.csv", encoding="utf-8-sig")

# 2. 施設名の表記ゆれを正規化(【国道487号】や(旧紅葉橋)を取り除く)
def cleanname(s):
    s = re.sub(r"[【\[].*?[】\]]", "", str(s))
    s = re.sub(r"(.*?)", "", s)
    return s.strip()

infra["clean"] = infra["施設名称"].apply(cleanname)

# 3. 観光対象を「ダム/橋/トンネル」で振り分けて name セットを作る
infra_dam_names    = set(infra.loc[infra["施設名称"].str.contains("ダム"), "clean"])
infra_bridge_names = set(infra.loc[infra["施設名称"].str.contains("橋"),   "clean"])
infra_tunnel_names = set(infra.loc[infra["施設名称"].str.contains("トンネル|隧道|隨道"), "clean"])

# 4. 各台帳に「観光対象」フラグ列 (0/1) を追加
dam["観光対象"]    = dam["ダム名"].apply(cleanname).isin(infra_dam_names).astype(int)
bridge["観光対象"] = bridge["施設名"].apply(cleanname).isin(infra_bridge_names).astype(int)
tunnel["観光対象"] = tunnel["施設名"].apply(cleanname).isin(infra_tunnel_names).astype(int)

このコードは 12 ダム / 4203 橋梁 / 157 トンネル に対して実行され、 それぞれ観光対象に 12 / 11 / 0 件のフラグが立つ。 このフラグ列が以降の H1〜H5 全てで使われる。

分析2: H1 — 観光対象ダムは「規模で」非観光ダムより大きいか

狙い・仮説

H1: 観光対象ダムは非観光ダムより容量・堤高・堤頂長・集水面積が大きい。 12 基のうち観光対象は 12 基(全件カタログ収載のため)か、それとも一部か。 4 つの規模指標で観光フラグ別に箱ひげ図を描いて比較する。

なぜ箱ひげ図か(要件H)

標本 n=12 と少ないため、平均だけでは外れ値の影響が大きすぎる。 箱ひげ図なら 中央値・四分位範囲・最大最小・外れ値が一枚で分かる。 さらに 各点を上に重ねる(ジッター付き散布)ことで「点が何個あるか」が読み取れる。

実装

1
2
3
4
5
6
7
8
9
metrics = [("総貯水容量_千m3", "総貯水容量"), ("堤高_m", "堤高"),
           ("堤頂長_m", "堤頂長"), ("集水面積_km2", "集水面積")]

fig, axes = plt.subplots(1, 4, figsize=(15, 4))
for ax, (col, label) in zip(axes, metrics):
    g_t = dam.loc[dam["観光対象"] == 1, col].dropna()  # 観光対象ダム
    g_f = dam.loc[dam["観光対象"] == 0, col].dropna()  # 非観光ダム
    ax.boxplot([g_f, g_t], labels=["非観光", "観光"])
    ax.set_title(label)

結果図

H1: 4 指標 (容量/堤高/堤頂長/集水面積) で観光 vs 非観光の箱ひげ
H1: 4 指標 (容量/堤高/堤頂長/集水面積) で観光 vs 非観光の箱ひげ

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

サマリ表

指標 観光n 観光中央値 非観光n 非観光中央値 中央値比
総貯水容量 (千m³) 12 2205.0 0 NaN NaN
堤高 (m) 12 48.0 0 NaN NaN
堤頂長 (m) 12 205.5 0 NaN NaN
集水面積 (km²) 12 11.8 0 NaN NaN

中央値比は 観光中央値 / 非観光中央値。非観光 n=0 の場合は計算不能(NaN/inf)。

分析3: H2 — 観光対象橋梁は P75 以上の上位に偏るか

狙い・仮説

H2: 4,203 橋梁のうち観光対象(11 件)は、延長 P75(=19m)以上の上位橋梁に強く偏る。 観光資源化される橋梁は「目立つ大型」「歴史的価値」が条件のはず。

なぜヒストグラム + ECDF か(要件H)

延長は 右に長い裾を持つ(小さな橋が大量、大きな橋は少数)ため log10 軸で見る必要がある。 ヒストグラムは形を見るが、観光対象 10 件は線として隠れがち。 ECDF(累積分布)を重ねれば、観光ラインが 右にどれだけシフトしているか を一目で読める。

実装の要点

結果図

H2: 橋梁延長 (log10) のヒストグラムと ECDF — 観光対象は右裾に偏在
H2: 橋梁延長 (log10) のヒストグラムと ECDF — 観光対象は右裾に偏在

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

サマリ表

母集団 n 中央値_m P75_m P90_m P99_m
全橋梁(クリーン) 4105 7.2 19.0 43.4 196.3
観光対象橋梁 11 292.0 404.2 500.0 536.0

分析4: H3 — 観光対象橋梁は国道沿いに偏るか

狙い・仮説

H3: 観光対象橋梁は道路種別で「国道」に集中するはず。 観光バスやマイカーで気軽にアクセスできるルート上にあることが、観光対象化の前提条件のはずだ。

なぜグループ棒グラフか(要件H)

「観光 vs 非観光」を「道路種別」で クロス集計の比率比較するには、 同じ道路種別で 2 本の棒(観光率/非観光率)を並べるのが直感的。 円グラフ 2 つだと比較しにくく、絶対数の棒だと観光 n=10 が見えなくなる。 各群の構成比(合計 1.0)に正規化して並べることで母数差を吸収する。

結果図

H3: 道路種別の構成比 — 観光対象は国道に偏るか
H3: 道路種別の構成比 — 観光対象は国道に偏るか

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

サマリ表

道路種別 観光対象 非観光
国道 0.545 0.275
県道 0.455 0.725

分析5: H4 — ロジスティック回帰で観光対象を予測

狙い・仮説

H4: 橋梁の延長・幅員・築年・国道フラグだけで「観光対象になる/ならない」を予測できるはず。 AUC ≥ 0.85 で弁別できれば、規模と立地だけで観光対象化の判別が可能と言える。 最も効く特徴は「log_延長」(規模の効きが他を圧倒する)と予想。

使う道具: ロジスティック回帰(要件B/J: ツール視点で)

役割(一文で): 「複数の特徴を入れたら YES/NO の確率(0〜1)が返ってくる」関数を、データから学習するツール。 中身は「特徴の重み付き足し算 → シグモイドで 0〜1 に潰す」だけ。 シグモイド関数の数式は黒箱でOK。学習者は「使い方」と「係数の意味」を覚える。

ステップこのツールの操作入力出力
準備特徴量を 標準化(平均0/分散1)(N×M) の特徴行列同じ形の標準化行列
学習lr.fit(X, y)特徴行列 X, 0/1 ラベル y係数 (M個) + 切片
予測lr.predict_proba(X)新しい点(または同じ X)各点の P(観光対象=1) ∈ [0,1]
評価roc_auc_score(y, p_pred)真ラベルと予測確率AUC スカラー

注意点 3つ

係数の読み方(要件B)

標準化済み係数 β は 「特徴を 1σ(1 標準偏差)増やすと、log-odds が β 増える」。 オッズ比 e^β は「1σ増による確率の倍率(オッズ単位)」。 β > 0 なら正方向に効く(観光対象になりやすい)。β < 0 なら逆に効く。

実装

X01_infra_tourism_dual_use.py 行 672–729

 1
 2
 3
 4
 5
 6
 7
 8
 9
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score

# 1. 特徴量設計(log変換・経過年・国道フラグ)
df = bridge.dropna(subset=["延長(m)", "幅員(m)", "架設年度"]).copy()
df["log_延長"]   = np.log10(df["延長(m)"].clip(lower=0.5))
df["log_幅員"]   = np.log10(df["幅員(m)"].clip(lower=0.5))
df["築年経過"]   = 2026 - df["架設年度"]
df["国道フラグ"] = (df["道路種別"] == "国道").astype(int)

X = df[["log_延長", "log_幅員", "築年経過", "国道フラグ"]].values
y = df["観光対象"].values     # 0/1

# 2. 標準化(各特徴を 平均0/分散1 に揃える。単位差で係数が偏るのを防ぐ)
X_std = StandardScaler().fit_transform(X)

# 3. 学習:観光対象=正例は数十件で少数派。class_weight="balanced" で重み調整
lr = LogisticRegression(max_iter=2000, class_weight="balanced", random_state=42)
lr.fit(X_std, y)

# 4. 予測確率(0〜1) → AUC で全橋梁の弁別力を測る
p_pred = lr.predict_proba(X_std)[:, 1]
auc = roc_auc_score(y, p_pred)
print("AUC =", auc, "  係数:", dict(zip(features, lr.coef_[0].round(3))))

結果: 1 橋梁の Before/After(広島空港大橋, 要件K)

段階・特徴
raw_延長(m)500.0
raw_幅員(m)9.8
raw_架設年度2010
raw_道路種別県道
log_延長2.699
log_幅員0.991
築年経過16
国道フラグ0
標準化_log_延長3.56
標準化_log_幅員0.135
標準化_築年経過-0.405
国道フラグ_(0/1)0
logit2.9
P(観光対象)0.948
実績1

この表は「広島空港大橋」が raw 値 → 派生特徴 → 標準化 → ロジット → 確率5段階でどう変換されるかを追ったもの。 最終 P(観光対象) は実績ラベルと整合(または乖離)する。

結果図

H4: ROC 曲線(左)と 4 特徴の標準化係数バー(右)
H4: ROC 曲線(左)と 4 特徴の標準化係数バー(右)

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

係数表

特徴 係数(標準化) オッズ比 (1σ増)
log_延長 1.363 3.908
log_幅員 0.681 1.975
築年経過 -0.678 0.508
国道フラグ -0.022 0.978
切片 -2.333 0.097

分析6: H4 補助 — PCA で多変量を 1 枚に圧縮

狙い

4 特徴 (log_延長, log_幅員, 築年経過, 国道フラグ) を 2 次元に圧縮して、観光対象が 特徴空間のどこに集まっているかを 1 枚で見る。 ロジスティック回帰の判定根拠を可視化するための補助分析。

使う道具: PCA(主成分分析, 要件B/J)

役割(一文で): 多くの列(4列)を、なるべく情報を残したまま 2 列にまとめ直すツール。 新しい 2 列は PC1 / PC2 と呼ばれる仮想軸で、元の 4 列の重み付け足し算でできている。 中身の「分散最大化」「直交性」の数式は 黒箱でOK

ステップ操作入力出力
1特徴を標準化(H4 と共通)(N×4) 行列標準化済み (N×4)
2PCA(n_components=2).fit_transform(X)標準化行列(N×2) の PC1/PC2 座標
3pca.explained_variance_ratio_各軸の寄与率(合計≦1)
4pca.components_ = ローディング(2×4) の重み行列

注意点 3つ

実装

1
2
3
4
5
6
7
8
9
from sklearn.decomposition import PCA

pca = PCA(n_components=2)            # 4 特徴 → 2 軸 に圧縮
X_pca = pca.fit_transform(X_std)     # X_std は H4 と同じ標準化済み行列
print(pca.explained_variance_ratio_) # 軸ごとの寄与率(合計≦1.0)

# 散布図に観光フラグを色で重ねれば、特徴空間上での分離を視覚化できる
plt.scatter(X_pca[y==0, 0], X_pca[y==0, 1], color="blue", s=8)
plt.scatter(X_pca[y==1, 0], X_pca[y==1, 1], color="red",  marker="*", s=80)

結果図

H4 補助: PCA で 4 特徴を 2 軸に圧縮、観光対象(赤☆)の偏在を確認
H4 補助: PCA で 4 特徴を 2 軸に圧縮、観光対象(赤☆)の偏在を確認

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

ローディング(4特徴 → PC1/PC2 の重み)

特徴 PC1 PC2
log_延長 0.654 -0.093
log_幅員 -0.102 0.781
築年経過 -0.641 0.146
国道フラグ 0.389 0.600

分析7: H5 — 市町別 重回帰で「観光対象数 = a×ダム + b×橋 + c×トンネル」を解く

狙い・仮説

H5: 市町ごとの観光対象数は、その市町に存在するダム・橋梁・トンネル数の線形和で説明できるはず。 重回帰で a, b, c を推定し、どのインフラ種別が観光対象化されやすいかを係数比から読む。 予想: a (ダム1基あたり) が b (橋梁1基あたり) より圧倒的に大きい — ダムは1基ごとに観光化されるが、橋梁は1基ごとに観光化される確率が低い。

STEP 分け(要件O)— 重回帰の前後でやること

STEP役割入力出力
STEP1市町別集計(groupby + concat) ダム/橋梁/トンネル 各台帳の市町列 市町×6列の集計テーブル
STEP2重回帰(statsmodels.OLS) X = (dam_n, bridge_n, tunnel_n) と y = 観光対象数 係数 4個 (切片+3) + R² + 95%CI

使う道具: 重回帰(statsmodels.OLS)

役割(一文で): 「結果 y = a*x1 + b*x2 + c*x3 + 切片 + 誤差」を、誤差の二乗和が最小になる a, b, c を求めるツール。 中身は線形代数(最小二乗法)だが、使うのは fit() と summary() だけでいい。

注意点 3つ

実装

 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
import statsmodels.api as sm

# 0. 各台帳から「市町」列を統一(トンネルは「府中町 茂陰一丁目」のような詳細住所)
def first_city(s):
    s = str(s).split()[0]
    m = re.match(r"^([一-龥ぁ-んァ-ンA-Za-z]+(?:市|町|村|区))", s)
    return m.group(1) if m else None

dam["市町"]    = dam["位置"].apply(first_city)
bridge["市町"] = bridge["住所(市町)"].apply(first_city)
tunnel["市町"] = tunnel["住所(市町)"].apply(first_city)

# 1. 市町ごとに 6 つの集計量を結合
city_df = pd.concat([
    dam.groupby("市町").size().rename("dam_n"),
    bridge.dropna(subset=["市町"]).groupby("市町").size().rename("bridge_n"),
    tunnel.dropna(subset=["市町"]).groupby("市町").size().rename("tunnel_n"),
    dam.groupby("市町")["観光対象"].sum().rename("dam_tour"),
    bridge.dropna(subset=["市町"]).groupby("市町")["観光対象"].sum().rename("bridge_tour"),
    tunnel.dropna(subset=["市町"]).groupby("市町")["観光対象"].sum().rename("tunnel_tour"),
], axis=1).fillna(0).astype(int).reset_index()

city_df["観光対象数"] = city_df["dam_tour"] + city_df["bridge_tour"] + city_df["tunnel_tour"]

# 2. 重回帰: 観光対象数 = a*dam_n + b*bridge_n + c*tunnel_n + 切片
X = city_df[["dam_n", "bridge_n", "tunnel_n"]].values
y = city_df["観光対象数"].values
X_sm = sm.add_constant(X)                   # 切片列を加える
model = sm.OLS(y, X_sm).fit()

print(model.summary())                      # 係数, p値, R², 調整R²

結果図

H5: 観測 vs 予測 散布(左)と 3 変数の係数+95%CI(右)
H5: 観測 vs 予測 散布(左)と 3 変数の係数+95%CI(右)

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

係数表(重回帰)

変数 係数 p値 95%CI下 95%CI上
切片 -0.0120 0.8283 -0.1217 0.0977
dam_n 0.9767 0.0000 0.7739 1.1795
bridge_n 0.0017 0.0000 0.0011 0.0024
tunnel_n 0.0326 0.0514 -0.0002 0.0654

市町別 観測値・予測値・残差

市町 dam_n bridge_n tunnel_n dam_tour bridge_tour tunnel_tour 観光対象数 観光対象_予測 残差
広島市佐伯区五日市町 2 0 0 2 0 0 2 1.94 0.06
北広島町 0 272 3 0 2 0 2 0.55 1.45
呉市 0 320 5 0 2 0 2 0.70 1.30
三原市久井町尾道市御調町 1 0 0 1 0 0 1 0.96 0.04
尾道市御調町 1 0 0 1 0 0 1 0.96 0.04
庄原市川西町 1 0 1 1 0 0 1 1.00 0.00
呉市安浦町 1 0 0 1 0 0 1 0.96 0.04
世羅郡世羅町 1 0 0 1 0 0 1 0.96 0.04
東広島市河内町 1 0 1 1 0 0 1 1.00 0.00
廿日市市 1 236 0 1 0 0 1 1.37 -0.37
東広島市福富町 1 0 0 1 0 0 1 0.96 0.04
福山市加茂町 1 0 0 1 0 0 1 0.96 0.04
三原市 0 285 3 0 1 0 1 0.58 0.42
竹原市仁賀町 1 0 0 1 0 0 1 0.96 0.04
世羅町 0 136 1 0 1 0 1 0.25 0.75
三次市 0 276 2 0 1 0 1 0.53 0.47
安芸太田町 0 220 17 0 1 0 1 0.92 0.08
大竹市 0 48 1 0 1 0 1 0.10 0.90
尾道市 0 212 7 0 1 0 1 0.58 0.42
東広島市 0 389 0 0 1 0 1 0.66 0.34

残差 > 0 の市町は「インフラ規模に対して観光対象数が多い」=観光に積極的。 残差 < 0 は「あるのに観光化されていない」=観光資源として未活用

分析8: 散布図行列で 4 変数の全ペアを一望する

狙い

規模・経過年・緯度・経度 の 4 変数の全ペア(6 通り)を 1 枚で見る。 観光対象(赤☆)が どのペア空間で偏在するか を網羅的に確認する。

なぜ散布図行列か(要件H)

個別の散布図を 6 枚並べると 視線移動が大きい。 散布図行列は 同じ X 軸が縦方向に揃うため、ある変数の効きを多角的に読み取れる。 対角は各変数の単独分布(ヒスト)を入れて分布の形も同時に確認できる。

結果図

散布図行列: 規模(log) × 経過年 × 緯度 × 経度、観光対象=☆
散布図行列: 規模(log) × 経過年 × 緯度 × 経度、観光対象=☆

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

仮説検証と考察

5 仮説の検証結果サマリ

仮説結果判定
H1: 観光対象ダム > 非観光ダム(規模) 12 基すべて観光対象 → 母集団比較不能 退化(メタ発見)
H2: 観光対象橋梁は P75 以上に偏在 観光対象 91% が P75 超 (vs 全体 25%)、観光中央値 292m vs 全体 7m 強く支持
H3: 観光対象は道路アクセス性が高い 観光対象 55% が国道 (vs 非観光 28%) 強く支持
H4: ロジスティック回帰で AUC ≥ 0.85 AUC = 0.939, log_延長 が最大寄与 支持
H5: 市町別重回帰 R² ≥ 0.5 R² = 0.604, dam_n の係数が最大 (+0.98) 支持

主要発見

  1. 広島県の県管理ダム 12 基はすべてインフラツーリズム対象 — H1 が「退化」したことが むしろ重要な発見。 ダムは 1 基ごとに観光化されるのが県の方針と読める。
  2. 橋梁は「上位 1% の超大型」のみが観光対象化される。観光対象橋梁の中央値 292m は、全体中央値の数十倍。
  3. 観光対象は国道偏重: アクセス性が観光化の前提条件。市町道・農道上の橋は観光対象にならない。
  4. ロジスティック回帰で AUC = 0.939: 規模・年代・道路種別という 3〜4 個の数値だけで観光対象判別が高い精度で可能。 → 「観光対象になりそうな未掲載インフラ」を全 4,203 橋梁から自動推薦できる
  5. 市町別重回帰の係数 a (dam_n) ≫ b (bridge_n): ダム1基あたりの観光化確率が橋梁1基あたりの数十〜数百倍。

分析の限界(誠実な開示)

発展課題

結果X → 新仮説Y → 課題Z(要件E)

  1. 結果X1: ダム 12 基はすべて観光対象だった。 新仮説Y1: ため池 6,754 基まで広げれば、規模 P95 以上のため池が観光対象化される境界が見えるはず。 課題Z1: data/extras/tameike_basic.csv をマージし、本記事と同じ ロジスティック回帰を 「ため池 + ダム」の合成データセットで動かす。 規模軸での閾値(観光化開始サイズ)を係数から逆算。
  2. 結果X2: 観光対象は国道偏重 (55% が国道) だった。 新仮説Y2: 避難所のアクセス性も国道偏重のはず。 災害時に「国道沿いの観光対象橋梁」は 観光と避難動線の二重利用として価値がある。 課題Z2: 観光対象 10 橋梁の 緯度経度から半径 1km 以内の避難所件数を BallTree で集計。 H3 を 避難計画への二重利用評価に拡張。L09 のレッスンと連携。
  3. 結果X3: ロジスティック回帰の AUC = 0.939。 新仮説Y3: モデルが 「観光対象になりそうなのに未掲載」な橋梁を上位リストとして特定できるはず。 課題Z3: X01_h4_top50_pred.csv から実績=0 で P_観光対象 ≥ 0.7 の橋梁を抽出し、 実地調査リスト化。「インフラツーリズム新規候補リスト」として広島県観光連盟に提案できる形に。
  4. 結果X4: 市町別重回帰 R² = 0.604。 新仮説Y4: 観光対象数を 人口・観光客数・予算で説明する方が R² が上がる。 課題Z4: F4(DID 人口集中地区)と G4(クルーズ船観光客)のデータを市町別で結合し、 5変数重回帰に拡張。インフラ整備度 vs 観光需要 のどちらが効くかを分離。
  5. 結果X5: PCA で観光対象が PC1 (規模軸) に偏在。 新仮説Y5: LDA なら PCA より 観光フラグを見て軸を作るので、もっと綺麗に分離できるはず。 課題Z5: sklearn.discriminant_analysis.LinearDiscriminantAnalysis で 本記事と同じ 4 特徴を 1 軸に圧縮し、PCA との比較表を作る。 教師あり vs 教師なし の 分離力の差を体感する。