ふくふくHukuhuku Inc.
EP.22Prep 13分公開: 2026-09-01

前処理をテストする ── 壊れたデータを下流へ流さない

コードは正しく動いているのに、入力が変わって結果が壊れる。前処理の事故はこの形が多い。何を検査し、どこで止めるかを整理します。

#前処理#テスト#データ品質#運用
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

データ基盤・データパイプライン構築 / BI / 生成 AI 活用支援を専門とするエンジニア (28 年)。 本記事は AI 利用ポリシーに基づき、生成 AI の補助で執筆 → 人間が監修・編集して公開しています。

プロフィール詳細
シェア

前処理の事故には特徴があります。コードは何も変えていないのに、ある日結果が壊れる。原因は入力側の変化です。

この回は ── 入ってくるデータ自体を検査する仕組み ── を扱います。コードのテストとは別に必要なものです。

1. コードのテストでは捕まらない

LLM時代のテスト戦略 で扱ったテストは、処理が正しいかを見るものでした。前処理では、それだけでは足りません。

コードのテストで捕まるもの・捕まらないもの
事故の原因コードのテストで捕まるか
処理の書き間違い捕まる
上流の列名が変わった捕まらない
値の意味が変わった捕まらない
件数が急に減った捕まらない
欠損が急に増えた捕まらない
新しい区分値が現れた捕まらない

2行目以降は、コードが正しくても起きます。しかも処理はエラーにならず、静かに間違った結果を出すことが多い。これが最も厄介な点です。

止まらないのが問題

エラーで止まってくれれば、その時点で気づけます。前処理の事故は止まりません。空の集計、半分の件数、全部が同じ区分。下流まで流れてから「数字がおかしい」と言われるという形で発覚します。

2. まず件数を見る

最も単純で、最も多くの異常を捉えるのが件数です。ここから始めてください。

件数の異常を、過去の実績と比べて検知する
Python
import numpy as np

def check_row_count(current: int, history: list[int],                    tolerance: float = 3.0) -> tuple[bool, str]:    """件数が過去の実績から外れていないかを見る。
    history: 直近の日次件数(新しい順でも古い順でもよい)    tolerance: 何倍のばらつきまで許すか    """    if len(history) < 7:        return True, "履歴が不足(判定なし)"
    h = np.array(history, dtype=float)    median = np.median(h)    # 外れ値に強い散らばりの指標を使う    mad = np.median(np.abs(h - median))    spread = mad * 1.4826 or 1.0      # 0除算を避ける
    z = abs(current - median) / spread
    if z > tolerance:        direction = "多すぎ" if current > median else "少なすぎ"        return False, (f"件数が{direction}: {current:,} 件 "                       f"(平常時 {median:,.0f} 件 / 乖離 {z:.1f})")    return True, f"正常: {current:,} 件"

history = [10200, 9800, 10100, 10500, 9900, 10300, 10000]for n in [10150, 5200, 0, 30000]:    ok, msg = check_row_count(n, history)    print(f"  {'OK ' if ok else 'NG '} {msg}")

中央値と、外れ値に強い散らばりの指標を使っているのが要点です。平均と標準偏差だと、過去に一度でも異常があると、その分だけ判定が緩くなります。一度大きく外れた日があると、その後しばらく検知できなくなる ── という形で、異常が異常を隠します

そして0件も必ず検知してください。「取り込みが失敗して0件」は最も多い事故の1つですが、処理としてはエラーになりません。空のデータを正常に処理して、空の結果を出します。

3. 何を検査するか

件数の次に見るべき項目を、効果の大きい順に並べます。

検査項目と効果
検査捉えられる異常手間
件数取り込み失敗、重複、欠落小さい
列の有無と型上流のスキーマ変更小さい
欠損率取得できなくなった項目小さい
値の範囲単位の変更、桁の誤り(EP.21)
区分値の集合新しい区分の出現
主キーの重複結合で件数が増える原因
集計値の水準全体的な異常

3行目が実務で効きます。ある日から特定の列が全部空になるというのは、上流の変更でよく起きます。件数は変わらないので、件数の検査では捕まりません。

5行目も重要です。新しい区分値が現れると、それを想定していない後段の処理が誤動作します。EP.19 で扱った「学習時に存在しなかった群」と同じ問題です。

複数の検査をまとめて実行する
Python
from dataclasses import dataclass

@dataclassclass CheckResult:    name: str    passed: bool    message: str    severity: str    # "error" は止める / "warn" は通す

def run_checks(df, spec) -> list[CheckResult]:    results = []
    # 1. 必要な列があるか    missing = set(spec["required_columns"]) - set(df.columns)    results.append(CheckResult(        "必須列", not missing,        f"欠けている列: {sorted(missing)}" if missing else "すべて存在",        "error",    ))    if missing:        return results     # 列が無ければ以降は判定できない
    # 2. 欠損率    for col, max_null in spec.get("max_null_rate", {}).items():        rate = df[col].isna().mean()        results.append(CheckResult(            f"欠損率:{col}", rate <= max_null,            f"{rate:.1%}(上限 {max_null:.0%})", "error",        ))
    # 3. 値の範囲    for col, (lo, hi) in spec.get("ranges", {}).items():        out = df[(df[col] < lo) | (df[col] > hi)][col]        results.append(CheckResult(            f"範囲:{col}", out.empty,            f"範囲外 {len(out)} 件" if len(out) else "すべて範囲内", "error",        ))
    # 4. 想定外の区分値(警告に留める)    for col, allowed in spec.get("allowed_values", {}).items():        unknown = set(df[col].dropna().unique()) - set(allowed)        results.append(CheckResult(            f"区分値:{col}", not unknown,            f"未知の値: {sorted(unknown)}" if unknown else "既知のみ", "warn",        ))
    # 5. 主キーの重複    if key := spec.get("primary_key"):        dup = df.duplicated(subset=key).sum()        results.append(CheckResult(            "主キー重複", dup == 0, f"重複 {dup} 件", "error",        ))
    return results

`severity` で止めるか通すかを分けているのが要点です。次の節で扱いますが、全部を止める設定にすると、運用が回らなくなります

4. 止めるか、通すか

検査に引っかかったとき、処理を止めるか、警告して通すかを決める必要があります。

止めるか通すかの判断
状況判断理由
必須列が無い止める処理のしようがない
件数が0止める取り込み失敗の可能性が高い
主キーが重複止める下流で件数が膨らむ
範囲外の値止める集計が狂う
新しい区分値警告正当な追加かもしれない
欠損率がやや上昇警告一時的な変動の可能性

判断の基準は、壊れたデータが下流へ流れる影響です。集計値が広く使われるなら止める、参考情報なら警告に留める。

全部を止めると無視されるようになる

厳しくしすぎると、しょっちゅう止まるようになります。そして「またか」と再実行で通す運用になり、本物の異常も同じ扱いになりますLLM時代のテスト戦略 EP.11 で扱った「赤に慣れる」問題と、まったく同じ構造です。

5. 上流と取り決める

検査は事後の防御です。根本的には、上流と取り決めておくほうが効きます。 という考え方です。

  • 列名と型 — 変えるときは事前に連絡する
  • 必須かどうか — 空になりうる列を明示
  • 取りうる値 — 区分を増やすときは連絡
  • 単位と桁 — EP.21 で扱った内容
  • 件数の目安 — 大きく変わるときは連絡

取り決めがあると、変わったこと自体が検知できます。取り決めがなければ、変わったかどうかも分かりません。「上流が黙って変えた」という話は、たいてい取り決めがなかったという話でもあります。

現実には、上流が社外だったり、こちらから依頼できなかったりします。その場合でも、こちらの想定を文書として持っておく価値はあります。検査の仕様が、実質的な取り決めの記録になります。

6. 検知したあとの動線

検知しても、誰も見なければ意味がありません。動線まで設計してください。

  1. 1誰に知らせるか — 担当を決める。全員宛は誰も見ない
  2. 2何を見れば判断できるか — 検査の結果と、その時のデータの様子
  3. 3止まったときの手順 — 誰が判断し、どう再開するか
  4. 4警告が続いたときの扱い — 放置されないよう、期限を設ける

4番目が抜けやすい。警告は止まらないので、放置されます。「新しい区分値が出ました」という警告が数か月出続けているのに、誰も対応していない ── これはよくある状態です。警告にも期限を設けるか、定期的に棚卸ししてください。

そして、壊れたまま流れた場合にどう気づくかも考えておいてください。検査は完全ではありません。レガシー再生の現場 EP.06 で扱った突き合わせのように、下流側での確認も組み合わせると、取りこぼしが減ります。入口で全部を止めようとするより、入口と出口の両方で見るほうが現実的です。

ここまでのまとめ

前処理の事故は入力の変化から起きるので、コードのテストでは捕まらない。しかも止まらずに静かに壊れる。まず件数を見る(0件も必ず検知)。次に列・欠損率・範囲・区分値・主キー止めるか通すかは下流への影響で分け、全部止めると無視されるようになる。根本的には上流との取り決めが効きます。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp