Lesson 306

X06 14日豪雨期間の県内雨量分布 — 地理時空間 small multiples で時間×空間を同時に観る

雨量small multiples地理時空間ジニ係数ローレンツ曲線X-tier
所要 60〜80分 / 想定レベル: L01 水準 (中級) / データ: DoBoX #1275 (14日分) + stations_master.csv

データ取得手順

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

IDデータセット名
#222dataset #222
#333dataset #333
#444dataset #444
#888都市計画区域情報_区域データ_安芸高田市_行政区域
#999dataset #999
#1275観測情報_雨量日集計
#1450道路法面

実行コマンド:

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

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

学習目標と問い

【本記事のスタイル: 地理時空間 small multiples 研究】 14日分の空間分布を 4×4 グリッドで並列表示し、時間と空間を同時に観る。 PCA や重回帰よりも「並列地理散布」を可視化の主軸に据え、 時間断面の比較によって豪雨日の空間パターン異質性を明らかにする。

本レッスンは 2024 年 6 月 29 日 〜 7 月 12 日 の 14 日 にかけて広島県内で起きた 豪雨期間を対象に、県内 386 観測所 × 14 日 の日合計雨量を 1 枚の図に 14 コマ並列で並べ、「時間 × 空間」を同時に観る small multiples 研究記事である。X 水準 (= 既存 L レッスンの上位互換) として、 PCA や重回帰よりも「並列地理散布」を主軸の可視化に据える。

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

「14 日豪雨期間中、雨はどの場所にどの日にどれだけ降ったか — 空間と時間の偏りを 1 枚の図で示せるか?」

立てた仮説 (H1〜H5)

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

到達点

本記事を読み終えると、学習者は次の 5 つを身につけて帰る:

  1. small multiples の読み方: 同縮尺・同色軸の 14 コマを並べると、コマ間で「変わったこと / 変わっていないこと」が秒で分かる。
  2. 14 コマを 1 コマで描いてはいけない理由: 1 コマに重ねると線が混ざって読めない。並列表示こそが時間×空間を同時に観る正解。
  3. パレート不均衡の検証手順: ジニ + ローレンツ + 上位寄与率 + ヒスト の 4 点セット。
  4. 地理仮説の最低限の検証フロー: 緯度ヒスト重ね描き → 単回帰の傾き → 群間平均差。3 つを 1 枚にまとめる。
  5. 1 観測所の追跡: マトリクスから 1 行を抜き出し 14 日連続データを表で見ると、平均値や中央値では消える「瞬発の山」が見える。

なぜ small multiples が主軸なのか (要件 H: 図選択の理由)

14 日 × 290 観測所 ≒ 4000 セルの数値を 1 枚で見せたいとき、 代替案は (A) ヒートマップ (386行 × 14 列), (B) 折れ線 14 本重ね描き, (C) 14 日累積 1 枚地図, の 3 つがある。それぞれの限界:

これに対し small multiples は、地理を保ったまま (緯度経度散布)、時間を分離したまま (14コマ)、同じスケールで (vmax 共通) 描くので、3 つの限界をすべて回避する。本記事の 図1 がそれである。

使用データ

本レッスンは DoBoX 2 系統 + ローカル 1 系統 = 計 16 ファイルを使う。

原データ

ID名称形式件数 / 規模役割
#1275雨量10分値 2024年度 (14 日分)CSV × 14各 5–8 MB10 分雨量 → 日合計の元
stations_master.csvCSV569 観測所 (緯度経度あり)観測所名→緯度経度・市町
#1450camera_list.csv (補助参照のみ)CSV本記事では使用しない (拡張用)

サイズの整理 (要件 L):

行数列数説明
原データ rain_2024-XX-XX.csv (1日分)144 (=10分×24h)~290 観測所 + ヘッダ10 分値の 時刻×観測所
parse_rain_csv 後 (1日分)144~290tidy DataFrame, NaN は欠測
日合計 Series (1日分)~290観測所ごとに 1 値 (sum)
本記事の中核行列 M393 観測所14 日観測所×日 の日合計雨量
M_geo (緯度経度マージ後)38618マスタにある観測所のみ
14日累積ランク表3866rank, station, lat, lon, total_14d, cum_share

「観測所×日」と「観測所×時刻」は別物。日合計に集約することで 14×144=2016 列を 14 列に落とし、メモリを 1/100 にする。これが「逐次処理」の中身。

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

原データ (DoBoX)

論題データセットDL保存先形式サイズ
雨量10分値 2024-07-01 (代表)DoBoX #1275ページから DL ボタンdata/rain_2024/rain_2024-07-01.csvCSV10分値
雨量10分値 14日分 (一括)DoBoX #1275ページから DL ボタンdata/rain_2024/rain_2024-*.csvCSV × 14計 70-100 MB

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

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

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

stations_master.csv (緯度経度) はリポジトリ同梱。本スクリプト初回実行時、雨量 14 ファイルが無ければ ensure_dataset() が DoBoX から自動取得する。

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

図 PNG (HTML から直 DL)

再現スクリプト

X06_rainfall_14days_geo_smallmult.py を以下で実行:

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

分析1: 14日 CSV を逐次処理して観測所×日マトリクスを作る (要件K)

狙い

14 日分の rain_2024 CSV はそれぞれ (時刻 × 観測所) の 2 次元データだが、 14 日分を一気にロードすると 14×144×290 ≒ 580k セルのテーブルになり、 メモリと処理時間を消費する。本記事では各日の 10 分値を読み込んだ その場で日合計に 集約し、生 10 分値を即破棄する 逐次処理でメモリを抑える。

手法 (3 段階)

  1. 逐次ロード: parse_rain_csv() で各日の (時刻 × 観測所) tidy DF を作る。重複観測所名は groupby で和。
  2. 日合計に集約: tidy.sum(axis=0, min_count=1) で 144 行 → 1 行、観測所別の 日合計 Series を取り出す。生 10 分値はここで破棄する (del tidy)。
  3. 14 Series を concat: 14 日分の Series を pd.concat(axis=1)観測所 × 14 日のマトリクス M に結合。観測所マスタ stations_master.csv の緯度経度を merge で付与。

実装

X06_rainfall_14days_geo_smallmult.py 行 714–1118

 1
 2
 3
 4
 5
 6
 7
 8
 9
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
from pathlib import Path
import pandas as pd
from _common import parse_rain_csv

RAIN_DIR = Path("data/rain_2024")
files = sorted(RAIN_DIR.glob("rain_2024-*.csv"))   # 14 ファイル

daily_series = []
for f in files:
    tidy = parse_rain_csv(f)                # (時刻 × 観測所) の10分値
    # 同名観測所が複数列ある (parse_rain_csv が _2 等を付ける) ので groupby で和
    tidy.columns = [c.split("_")[0] if "_" in c else c for c in tidy.columns]
    tidy = tidy.groupby(axis=1, level=0).sum(min_count=1)
    daily = tidy.sum(axis=0, min_count=1)   # 観測所別の日合計 (Series)
    daily.name = f.stem.replace("rain_", "")
    daily_series.append(daily)
    del tidy                                # ここで生10分値を破棄 → メモリ解放

# 14 日分を観測所×日 マトリクスに結合
M = pd.concat(daily_series, axis=1).fillna(0.0).sort_index(axis=1)
print(M.shape)   # (観測所数, 14)

# 観測所マスタ (緯度経度) と結合
sm_raw = pd.read_csv("data/extras/stations_master.csv",
                     header=None, encoding="utf-8-sig", skiprows=3)
geo = pd.DataFrame({
    "station": sm_raw.iloc[:, 15].astype(str).str.strip(),
    "city":    sm_raw.iloc[:, 18].astype(str).str.strip(),
    "lat":     pd.to_numeric(sm_raw.iloc[:, 20], errors="coerce"),
    "lon":     pd.to_numeric(sm_raw.iloc[:, 21], errors="coerce"),
}).dropna(subset=["lat", "lon"])
geo = geo.drop_duplicates(subset="station", keep="first")
M_geo = M.reset_index().merge(geo, on="station", how="inner")
M_geo["total_14d"] = M_geo[M.columns.tolist()].sum(axis=1)

結果 (表と読み取り) — 日別県内統計

逐次処理で得られた日別の県内統計を 1 表に並べると、豪雨日と平常日の落差が見える:

表1: 日別県内統計 (14 日)

date n_stations max_mm mean_mm sum_mm p95_mm
2024-06-29 393 24.0 4.76 1872.0 13.0
2024-06-30 393 165.0 33.55 13186.0 66.6
2024-07-01 393 219.0 73.52 28892.0 132.4
2024-07-02 393 82.0 23.99 9429.0 50.4
2024-07-03 393 6.0 0.08 31.0 1.0
2024-07-04 393 4.0 0.10 41.0 1.0
2024-07-05 393 1.0 0.00 1.0 0.0
2024-07-06 393 0.0 0.00 0.0 0.0
2024-07-07 392 0.0 0.00 0.0 0.0
2024-07-08 393 0.0 0.00 0.0 0.0
2024-07-09 393 11.0 0.20 79.0 1.0
2024-07-10 393 182.0 56.46 22188.0 88.0
2024-07-11 393 91.0 28.21 11086.0 58.0
2024-07-12 393 41.0 14.24 5596.0 33.2

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

1 観測所の 14 日連続データ追跡 (要件K: Before/After 1 件追跡)

マトリクス M から 1 観測所だけを抜き出して 14 日の連続データを並べると、 平均や中央値では消える「瞬発の山」が時系列で見える。 本記事では 14 日累積 1 位の 河内 (東広島市, 34.472°N 132.892°E) を追跡対象にする。

表2: 河内 の 14 日連続日合計と累積 (要件K)

date daily_mm cum_mm
2024-06-29 5.0 5.0
2024-06-30 50.0 55.0
2024-07-01 219.0 274.0
2024-07-02 49.0 323.0
2024-07-03 0.0 323.0
2024-07-04 0.0 323.0
2024-07-05 0.0 323.0
2024-07-06 0.0 323.0
2024-07-07 0.0 323.0
2024-07-08 0.0 323.0
2024-07-09 0.0 323.0
2024-07-10 182.0 505.0
2024-07-11 78.0 583.0
2024-07-12 41.0 624.0

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

分析2: small multiples — 14 日地理散布を 4×4 グリッドで一括表示 (要件H)

狙い

14 日 × 県内 386 観測所の日合計雨量を 1 枚の図に詰め込み、 「どの日にどこでどれだけ」降ったかを 視覚で同時把握する。 本記事の主役図であり、他のすべての図 (ランキング棒・ローレンツ・緯度ヒスト) は この図から派生する仮説検証の役割を担う。

手法

なぜ small multiples が答えか (4 案比較, 要件H)

14 日 × 290 観測所のような 時間×空間データを 1 枚に圧縮する候補は 4 つあった。 本記事が small multiples を選んだ理由は以下の表のとおり:

方式時間情報空間情報マッピング負荷判定
(A) 386 行 × 14 列ヒートマップ (L05 で採用)◎ 列で表現× 観測所が縦並びで地理がつぶれる地理仮説 (H1, H5) が検証できない
(B) 折れ線 386 本重ね描き◎ x軸が時間× 線が混ざって誰がどこか分からない不採用
(C) 14 日累積を 1 枚地図× 時間情報が消えるH2 (空間パターン日次変化) が見えない
(D) small multiples 14 コマ並列地理散布 ★◎ コマ番号が時間◎ 各コマで地理保持採用 (H1〜H5 全てに対応)

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
import matplotlib.pyplot as plt
import numpy as np

n_days = len(date_cols)         # 14
fig, axes = plt.subplots(4, 4, figsize=(15.5, 16.0))

# 全コマ共通の地理ウィンドウと色軸 (= 比較しやすさの担保)
xlim = (geo["lon"].min()-0.05, geo["lon"].max()+0.05)
ylim = (geo["lat"].min()-0.05, geo["lat"].max()+0.05)
SIZE_MAX = M_geo[date_cols].values.max()
cmap = plt.get_cmap("YlOrRd")

def size_of(v):
    return 8.0 + 480.0 * (v / SIZE_MAX) if v > 0 else 4.0

for idx, ax in enumerate(axes.flat):
    if idx < n_days:
        d = date_cols[idx]
        v = M_geo[d].values
        sizes  = np.array([size_of(x) for x in v])
        colors = cmap(np.clip(v / SIZE_MAX, 0, 1))
        ax.scatter(M_geo["lon"], M_geo["lat"], s=sizes, c=colors,
                   edgecolor="#333", linewidths=0.25, alpha=0.85)
        ax.set_title(f"{d}\n max={v.max():.0f}mm  mean={v.mean():.1f}mm")
    elif idx == 14:
        # 14日累積まとめコマ
        v = M_geo["total_14d"].values
        ...
    else:
        # 凡例コマ
        ...
    ax.set_xlim(*xlim); ax.set_ylim(*ylim)
    ax.set_xticks([]); ax.set_yticks([])
    ax.set_aspect("equal", adjustable="box")
plt.savefig("assets/X06_small_multiples.png", dpi=130)

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

図1 (主役): 14日 × 386観測所 の地理散布並列表示。マーカー位置は固定、サイズと色のみ日合計の関数。
図1 (主役): 14日 × 386観測所 の地理散布並列表示。マーカー位置は固定、サイズと色のみ日合計の関数。

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

結果 (表と読み取り) — 14 日累積上位10

表3: 14日累積雨量 上位10観測所

rank station city lat lon total_14d cum_share
1 河内 東広島市 34.472 132.892 624.0 0.69
2 吉和 廿日市市 34.482 132.134 579.0 1.32
3 小梨 竹原市 34.371 132.914 485.0 1.86
4 仁賀 三次市 34.791 132.978 474.0 2.38
5 比和 庄原市 34.983 132.986 457.0 2.88
6 西野 三原市 34.406 133.059 427.0 3.35
7 小瀬川ダム 廿日市市 34.309 132.124 406.0 3.80
8 中道(国) 廿日市市 34.368 132.086 397.0 4.24
9 矢草(砂防) 廿日市市 34.334 132.267 378.0 4.66
10 頓原 nan 34.452 132.088 375.0 5.07

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

分析3: 14日累積ランキング — 上位30観測所の棒 + 累積寄与曲線

狙い

図1 で見えた「北部に偏った大粒マーカー」を、個別観測所ベースで順位付けして読む。 さらに 累積寄与曲線で「上位何観測所で全体雨量の何%を占めるか」を視覚化し、 H3 (パレート的不均衡) と H4 (県内不均一性) の準備を整える。

手法

実装

X06_rainfall_14days_geo_smallmult.py 行 886–904

886
887
888
889
890
891
892
893
top30 = rank.head(30)
norm_lat = (top30["lat"]-geo["lat"].min())/(geo["lat"].max()-geo["lat"].min()+1e-6)
bar_colors = plt.get_cmap("coolwarm")(1.0 - norm_lat.values)  # 反転で北=青

fig, axes = plt.subplots(1, 2, figsize=(14, 8.0),
                         gridspec_kw={"width_ratios":[1.4, 1.0]})
axes[0].barh(np.arange(len(top30))[::-1], top30["total_14d"], color=bar_colors)
axes[1].plot(rank["rank"], rank["cum_share"]*100, color="#0969da", lw=1.6)

結果 (図と読み取り)

図2: 上位30観測所棒 (色=緯度) + 累積寄与曲線
図2: 上位30観測所棒 (色=緯度) + 累積寄与曲線

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

分析4: 日別県内統計の折れ線 — 14 日のうちどの日に降ったか (H3)

狙い

small multiples (図1) のコマを順番に並べて見ても 「どの日が突出していたか」の数字が読みにくい。 ここでは 3 つの代表値 (最大・P95・平均) を 同じ図に重ねた折れ線として描き、 さらに県内総雨量を 背景の灰色棒で添えて、1 図 4 系列で 14 日の時間パターンを示す。

手法

実装

X06_rainfall_14days_geo_smallmult.py 行 923–937

923
924
925
926
927
928
fig, ax = plt.subplots(figsize=(12, 5.4))
ax2 = ax.twinx()
ax.plot(day_stats["date"], day_stats["max_mm"], "-o", color="#cf222e", label="県内最大")
ax.plot(day_stats["date"], day_stats["p95_mm"], "--s", color="#fb8500", label="県内 P95")
ax.plot(day_stats["date"], day_stats["mean_mm"], "-^", color="#0969da", label="県内平均")
ax2.bar(day_stats["date"], day_stats["sum_mm"], color="#888", alpha=0.22, label="総雨量")

結果 (図と読み取り)

図3: 日別県内統計 (最大・P95・平均・総雨量) の 14 日折れ線
図3: 日別県内統計 (最大・P95・平均・総雨量) の 14 日折れ線

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

分析5: ローレンツ曲線とジニ係数 — 県内不均一性を 1 数字で表す (H4)

狙い

図2 で見えた「上位10観測所が 5% を占める」を 1 つのスカラー指標に圧縮し、 他県・他期間との比較に耐える形にする。所得分布の不平等度から借りた ジニ係数を使う。

手法

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import numpy as np

vals = np.sort(rank["total_14d"].values)         # 昇順
n = len(vals)
cum = np.cumsum(vals)
# ローレンツ点列 (0,0) を頭に追加
xs = np.r_[0.0, np.arange(1, n+1) / n]
ys = np.r_[0.0, cum / cum[-1]]
gini = 1.0 - 2.0 * np.trapezoid(ys, xs)
print(f"ジニ係数 G = {gini:.3f}")

結果 (図と読み取り)

図4: 14日累積雨量のヒスト (左) + ローレンツ曲線 (右)。G=0.120
図4: 14日累積雨量のヒスト (左) + ローレンツ曲線 (右)。G=0.120

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

分析6: 多雨群 vs 少雨群の緯度ヒスト重ね描き — H5 直接検証

狙い

H1 (多雨は北部に集中) と H5 (多雨と少雨は地理的に分離) は密接だが別物。 H1 は全観測所の累積雨量 vs 緯度の関係 (=単回帰の傾き)、 H5 は多雨群と少雨群の地理分布が重ならないかの検証 (=緯度ヒストの重なり度)。 本セクションで H5 を 緯度ヒストの重ね描き + 散布 の 2 パネルで仕上げる。

手法

実装

X06_rainfall_14days_geo_smallmult.py 行 450–478

 1
 2
 3
 4
 5
 6
 7
 8
 9
459
460
461
462
q75 = rank["total_14d"].quantile(0.75)
q25 = rank["total_14d"].quantile(0.25)
heavy = rank[rank["total_14d"] >= q75]
light = rank[rank["total_14d"] <= q25]

fig, axes = plt.subplots(1, 2, figsize=(13, 5.4))
bins = np.linspace(geo["lat"].min(), geo["lat"].max(), 22)
axes[0].hist(heavy["lat"], bins=bins, color="#cf222e", alpha=0.55, label="多雨群")
axes[0].hist(light["lat"], bins=bins, color="#0969da", alpha=0.55, label="少雨群")
axes[1].scatter(rank["lat"], rank["total_14d"], s=14, c="#bbb", alpha=0.6)
axes[1].scatter(heavy["lat"], heavy["total_14d"], c="#cf222e")
axes[1].scatter(light["lat"], light["total_14d"], c="#0969da")
m_slope = np.polyfit(rank["lat"], rank["total_14d"], 1)

結果 (図と読み取り)

図5: 多雨群・少雨群の緯度ヒスト重ね描き (左) + 緯度×累積雨量散布 (右)
図5: 多雨群・少雨群の緯度ヒスト重ね描き (左) + 緯度×累積雨量散布 (右)

表4: 多雨群・少雨群・全体の地理統計

観測所数 14日累積平均(mm) 緯度平均(°N) 経度平均(°E)
多雨群 (≥P75) 97 306.3 34.442 132.454
少雨群 (≤P25) 97 181.2 34.622 132.814
全体 386 235.2 34.561 132.732

この図と表から読み取れること (仮説と逆の発見):

考察・限界・発展課題

仮説 H1〜H5 の判定まとめ

仮説判定根拠
H1 上位観測所は 北部山間部に集中 反証 (むしろ南寄り) 上位30緯度平均 34.416 vs 全体 34.561, 単回帰傾き -64 mm/°N (図2/図5)。仮説と逆向きの結果 = 2024-07 期間は南寄り (中国山地南斜面) で多雨
H2 豪雨日と平常日で空間パターンが異なる 強支持 県内最大日合計が 219 mm/日 〜 完全無降雨 (3 日が 0 mm)。small multiples で空間分布の違いが視覚的に明白 (図1/図3)
H3 14 日のうち 2-3 日に集中 強支持 上位3日寄与率 69.6% (図3)
H4 累積雨量分散大 (G > 0.4) 反証 (期間累積では均質化) ジニ係数 G = 0.120, 上位10観測所寄与わずか 5.1%。14 日累積では観測所間の差が均される = 局地豪雨は時間方向で偏るが、期間累積では空間的に意外と均質
H5 多雨群と少雨群が地理的に分離 反証 (多雨群が南寄り) 多雨群緯度 34.442 - 少雨群 34.622 = -0.180° (図5)。多雨群が南寄りに偏る発見 = 「北部山間部仮説」を覆す

本記事の限界

発展課題 (本記事を踏み台にする 5 案)

課題1: 標高 (S31 標高図) を結合した 標高 × 雨量 散布

課題2: K-Means + 地理クラスタ で「雨パターン地域」を抽出

課題3: PCA で 14 日 → 2 主成分 に圧縮 (時間軸の主成分化)

課題4: 動画 (.gif アニメーション) として 14 コマを並べず連続再生

課題5: ロジスティック回帰 で「観測所の翌日豪雨」を予測

本記事のスタイル (small multiples) の汎用性

同じパターンは以下の DoBoX データにそのまま適用できる:

=> 「same axes, different time」を持つ任意の時空間データに対し、本記事の図1パイプラインがそのまま再利用可能。