Lesson 04

雨量CSV — データ前処理の解剖 (tidy 化)

基礎前処理現場データv2-rewrite
所要 100分 / 想定レベル: リテラシ基礎〜応用基礎 / データ: 観測情報_雨量日集計 (DoBoX #1275) 2024-07-01分 (resource_id=94500)

学習目標

使用データ

データ取得手順

論題データセットDL保存先形式サイズ
雨量10分値 2024-07-01DoBoX #1275直DLdata/rain_2024/rain_2024-07-01.csvCSV (5段ヘッダ, 1605列, UTF-8 BOM)約 1.8 MB

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

cd "2026 DoBoX 教材"
iwr "https://hiroshima-dobox.jp/resource_download/94500" -OutFile "data/rain_2024/rain_2024-07-01.csv"

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

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

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

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

⬇ L04_tidy_rainfall.py

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

方法

  1. header=None で全部「データ」として読込: 多段ヘッダがある CSV は素直に read_csv せず、まず生表として確保
  2. ヘッダ位置の動的検出: 各行の "10分雨量" 出現件数を数え、最大の行をヘッダとする (位置で取らず内容で取る)
  3. 観測所メタの抽出: ヘッダより上の行を meta DataFrame に分離 (事務所/所管/水系/河川/番号/観測所)。観測所ごとの4列繰り返しの中で 10分雨量列のみを残す
  4. 値部分の数値化と tidy 化: 10分雨量の列だけ抜き、観測所名を列名に、時刻を index に
  5. 完備性チェック: 観測所×時刻 の欠測ヒートマップ (黒=NaN) で、観測停止 / センサ故障 / 欠落を可視化
  6. フラグ列の解読: 0x... を整数に変換し、bit ごとに立っている件数を集計 → 各 bit が「観測あり/欠測/異常」のどれかを推測
  7. 整形品質指標: raw shape, tidy shape, 欠測率, ユニーク観測所数 をサマリ表に

コード解説

L04_tidy_rainfall.py 行 19–495

 1
 2
 3
 4
 5
 6
 7
 8
 9
64
65
66
67
68
69
70
71
72
55
56
57
58
59
60
61
62
63
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
import re
import numpy as np
import pandas as pd
from _common import parse_rain_csv, ensure_dataset

# === (0) データ自動取得 ===
SRC = "data/rain_2024/rain_2024-07-01.csv"
ensure_dataset(SRC, resource_id=94500, min_bytes=500_000)

# === (1) header=None で全部「データ」として読込 ===
raw = pd.read_csv(SRC, header=None, encoding="utf-8-sig")
print(raw.shape)  # (150行, 1605列) ← 401観測所 × 4列 + 時刻

# === (2) ヘッダ行の動的検出 ===
# 年度で位置が違う (2023年式: 5行目, 2024年式: 6行目) ので
# 「'10分雨量' を含む行」をスキャンして見つける。
header_idx = next(r for r in range(15)
                  if any("10分雨量" in str(v) for v in raw.iloc[r, :]))
obs_idx    = next(r for r in range(header_idx)
                  if str(raw.iloc[r, 0]).strip() == "観測所名")

# === (3) 各観測所は 4 列構造。10分雨量の列だけ拾う ===
cols_10min = [i for i, v in enumerate(raw.iloc[header_idx, :])
              if "10分雨量" in str(v)]   # 401 列
stations   = [str(raw.iloc[obs_idx, c]).strip() for c in cols_10min]

# === (4) 観測所メタを別 DataFrame に分離 ===
meta = pd.DataFrame({
    "office":  [str(raw.iloc[0, c]) for c in cols_10min],  # 事務所
    "system":  [str(raw.iloc[2, c]) for c in cols_10min],  # 水系
    "river":   [str(raw.iloc[3, c]) for c in cols_10min],  # 河川
    "station": stations,
})

# === (5) 値部分を数値化し tidy 化 ===
data = raw.iloc[header_idx + 1:, :].reset_index(drop=True)
ts   = pd.to_datetime(data.iloc[:, 0])
vals = data.iloc[:, cols_10min].apply(pd.to_numeric, errors="coerce")
tidy = pd.DataFrame(vals.values, index=ts, columns=stations)
# 重複観測所名は連番化 (`西部建設` が複数あるなど)

# === (6) 欠測ヒートマップ用に観測所×時刻のマスクを作る ===
miss_mat = tidy.isna().astype(int).T
miss_rate = tidy.isna().mean().mean() * 100
print(f"欠測率: {miss_rate:.2f}%")

# === (7) フラグ列 (0x...) の bit 解読 ===
flag_cols  = [c + 1 for c in cols_10min]                # 10分雨量 の隣がフラグ
flag_data  = raw.iloc[header_idx + 1:, flag_cols]
flag_int   = pd.Series([int(str(v), 16) for v in flag_data.values.ravel()
                        if pd.notna(v)], dtype=np.int64)
# bit ごとに 1 が立っている件数をカウント
bit_counts = [int(((flag_int >> b) & 1).sum()) for b in range(24)]
# 一番よく立つ bit が「観測あり」を表す可能性が高い

結果

① 生 CSV のスナップショット (先頭8行 × 先頭7列)

事務所名 西部建設 西部建設
データ所管 砂防課 気象台
水系名 太田川 太田川
河川名 天満川 京橋川
観測所番号 80 7437
観測所名 江波 広島(気)
観測時刻 10分雨量[mm] フラグ 累計雨量[mm] フラグ 10分雨量[mm] フラグ
2024/07/01 00:00 0.0 0x00000000 16.0 0x00010000 0.5 0x00000000

5 段ヘッダ + 観測時刻ヘッダ。各観測所は 4 列を占有 (10分雨量/フラグ/累計/フラグ)。空セル (NaN) が観測所間の繰り返しに見える。

② ヘッダ位置の動的検出スキャン

row first_cell n_10分雨量 is_観測所名行 is_河川名行
0 事務所名 0 False False
1 データ所管 0 False False
2 水系名 0 False False
3 河川名 0 False True
4 観測所番号 0 False False
5 観測所名 0 True False
6 観測時刻 401 False False
7 2024/07/01 00:00 0 False False
8 2024/07/01 00:10 0 False False
9 2024/07/01 00:20 0 False False
10 2024/07/01 00:30 0 False False
11 2024/07/01 00:40 0 False False
12 2024/07/01 00:50 0 False False
13 2024/07/01 01:00 0 False False
14 2024/07/01 01:10 0 False False

"10分雨量" 出現件数が最大の行 = ヘッダ行。 "観測所名" で始まる行 = 観測所名行。位置で取らず内容で取るので、 2023 年式 (5 段ヘッダ) でも 2024 年式 (6 段ヘッダ) でも壊れない。

③ tidy DataFrame (観測所×時刻, 先頭6行 × 先頭6観測所)

江波 広島(気) 広島(国) 牛田早稲田 中山新町 温品(砂防)
datetime
2024-07-01 00:00 0.0 0.5 0.0 0.0 1.0 1.0
2024-07-01 00:10 1.0 1.5 2.0 1.0 1.0 0.0
2024-07-01 00:20 0.0 0.5 0.0 1.0 0.0 1.0
2024-07-01 00:30 0.0 0.0 0.0 0.0 0.0 0.0
2024-07-01 00:40 0.0 0.0 0.0 0.0 0.0 1.0
2024-07-01 00:50 0.0 0.0 0.0 0.0 0.0 0.0

shape: 144 時刻 × 401 観測所。 1 行 = 1 時刻、1 列 = 1 観測所、値 = 10 分雨量 (mm)。

④ 観測所メタ DataFrame (先頭8件)

office owner system river sid station
西部建設 砂防課 太田川 天満川 80 江波
西部建設 気象台 太田川 京橋川 7437 広島(気)
西部建設 国土交通省 太田川 京橋川 13 広島(国)
西部建設 砂防課 太田川 二又川 86 牛田早稲田
西部建設 砂防課 太田川 中山川 85 中山新町
西部建設 国土交通省 太田川 戸坂川(矢口川) 65 温品(砂防)
西部建設 砂防課 太田川 小河原川 84 福木
西部建設 河川課 太田川 京橋川 1 西部建設

事務所 → 水系 → 河川 → 観測所 という 4 段の階層構造。観測所メタを別 DataFrame に切り出すと、 水系単位の集約 (L080) や河川単位の地図 (L02) と連携できる。

観測所階層: 水系 × 事務所 の観測所数ヒートマップ。同じ水系内に複数の事務所が観測網を持つ重層的な体制が見える
観測所階層: 水系 × 事務所 の観測所数ヒートマップ。同じ水系内に複数の事務所が観測網を持つ重層的な体制が見える
観測所×時刻 の欠測パターン (黒=NaN)。1 日通して欠測している観測所と、部分的に欠測する観測所がある。横筋が出ているのは「観測停止 (センサ故障)」のサイン
観測所×時刻 の欠測パターン (黒=NaN)。1 日通して欠測している観測所と、部分的に欠測する観測所がある。横筋が出ているのは「観測停止 (センサ故障)」のサイン
フラグ列の解読。(左) 各 bit が立っている割合 — 大半が 0x00000000 で全 bit=0 (=正常観測)。立つ bit はごく一部のレコードのみで、欠測/異常状態に対応すると推測できる。(右) フラグ値別の出現頻度 上位8
フラグ列の解読。(左) 各 bit が立っている割合 — 大半が 0x00000000 で全 bit=0 (=正常観測)。立つ bit はごく一部のレコードのみで、欠測/異常状態に対応すると推測できる。(右) フラグ値別の出現頻度 上位8
日合計上位5観測所の10分雨量 (上) と累積雨量 (下)。tidy DataFrame からは 1 行で <code>tidy.cumsum()</code> と書けて即座に可視化できる
日合計上位5観測所の10分雨量 (上) と累積雨量 (下)。tidy DataFrame からは 1 行で <code>tidy.cumsum()</code> と書けて即座に可視化できる

⑤ 整形品質サマリ

項目
生 CSV shape151 × 1605
生 CSV 列数の内訳1 (時刻) + 1604 (= 401 観測所 × 4 列)
ヘッダ行の動的検出row=6 で '10分雨量' を発見
観測所名行の検出row=5
メタ行 (事務所/所管/水系/河川/番号/観測所)row 0〜5
tidy 後 shape144 時刻 × 401 観測所
ユニーク観測所数 (重複名連番化前)393 → 連番化後 401
水系数 / 事務所数 / 河川数19 / 10 / 138
セル全体の欠測率0.667%
欠測のある観測所数40 / 401
フラグ列のユニーク値数3
フラグ最多値0x00000000 (57,359件)

考察

発展課題

  1. 14日分を結合した縦長 tidy: data/rain_2024/*.csv 全14ファイルを parse_rain_csv で読み込み、pd.concat で時系列を 14 日分に伸ばす。L080 と同じ前処理スタイル。
  2. 累計雨量との突合: tidy.cumsum() と CSV 内の累計雨量列を観測所ごとに比べて、仕様書なしで「累計雨量 = 当日 0:00 からの積算」を検証する。
  3. フラグ列の意味推定 → ラベル化: bit ごとの頻度から「観測ビット (常時1)」「欠測ビット (値が NaN の時のみ1)」を統計的に推定し、flag_meaning 列を tidy に追加した拡張版を作る。
  4. 観測所メタを使った空間集約: meta DataFrame を観測所名で結合して、水系別・河川別の日合計を算出。L080 のクロス相関分析の入力になる。
  5. ヘッダ仕様の自動診断レポート: 任意の DoBoX 系現場 CSV を投げると「ヘッダ何段か」「メタ列の意味推定」「データ開始行」を出力する 汎用パーサジェネレータを実装する。