tabular-cleanup について
tabular-cleanup は、乱雑な表を自律的に整えるスキルです。推定したデータ契約をユーザーが承認した後、決定的なチェックに基づいて修正し、安全な変換のみを採用して再実行可能なパイプラインと監査記録を作成します。
主な機能
- 01厳格なデータ契約の推定と確認
- 02決定的な違反チェックへのルール変換
- 03各反復で1つの pandas 変換を適用
- 04他のチェックを悪化させる変更の却下
- 05採用した変更を再実行可能なパイプラインに記録
- 06プロファイル、台帳、最終レポートの生成
活用シーン
- 01不統一なカテゴリ表記の統一
- 02型、形式、範囲外の値の修正
- 03必要な行や列を保ちながら表を整理
- 04監査可能で再現性のあるデータ整形
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ gaasher/ agent-loop-skills --skill tabular-cleanupインストール後、Claude Code で/tabular-cleanupを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/run.example.yamlschemas/ledger.schema.jsonprofile.schema.json詳細
表形式データのクリーンアップループ
汚れたデータダンプ(<artifact>)を、単一のエージェントが、正当化できる最もクリーンな状態に整えます。
実行開始後は、人間はループに介在しません。目的はスコアではなくチェックリストです。エージェントはデータ契約を推論し、それを決定論的な二値チェック(それぞれが重み付き浮動小数点数ではなく、違反の件数を報告します)にコンパイルします。続いて各イテレーションでテーブルのプロファイルを作成し、未解決のチェックから最も状態の悪いものを選び、解決するために pandas の変換を1つ適用します。そして、そのチェックの違反数が厳密に減少し、副次的な悪影響がない場合に限り、その変換を保持します。承認された変換はすべて再実行可能な pipeline.py に追記され、すべての試行は ledger に記録されます。作業は、構造(正しく解析し、整然としたテーブルを1つ作り、妥当な型にする)→ 契約の合成(観測された異常をすべてチェックに変換する)→ 修正ループに分けられます。品質を左右するのは契約の合成です。プロファイラーが気づいてもチェックにコンパイルされなかった問題(典型例は、1つのカテゴリに複数の表記があること)は、気づかれないまま残ります。汚れたデータが残っているのに、チェックリストがすべて合格になる状態です。チェックが読み取るのは保存された値です。そのため、正規化はループが実際に行うべき作業であり、チェック時だけの小細工ではありません。
使用するタイミング
汚れたテーブルを、自律的に推論したうえで確認済みの契約に沿ってクリーンアップする場合に使用します。すべての欠陥は、ループがゼロにするか、正直な残存項目として扱うべき決定論的チェックになります。厳密な契約推論をデフォルトにしてください(汚れたデータをすばやく「すべて合格」にしてしまう寛容な契約が、主な失敗要因です)。人間が確認するのはセットアップ時の契約確認だけで、その後は停止条件に達するまでループが実行されます。すでにクリーンなデータセットを対象とする自由形式の探索、既知の異常を1つだけ診断すること、外部の主張を情報源と照合することには使用しないでください。これらは分析ループです。このループはデータを書き換え、契約に適合させます。
セットアップ
バインディングを対話形式で解決します。作業ディレクトリに loop.run.yaml が存在する場合は読み込み、値を1行で確認してループに進みます。それ以外の場合、Claude Code では(AskUserQuestion ツールを使用できます)各バインディングの値を推定し、推奨オプションとして提示します。他のホストでは、それぞれを引用符付きのプレーンテキストプロンプトで質問します。その後、loop.run.yaml(形式:examples/run.example.yaml)を書き込み、ほかのファイルを作成する前に値を確認します。(以下の)契約は、ユーザーが明示的に承認しなければならない唯一の判断事項です。契約を推定したうえで、明示的な承認を得ます。それ以降はすべて自律的に進みます。
| バインディング | 意味 | デフォルト | 推定方法 |
|---|---|---|---|
<artifact> | クリーンアップする汚れたテーブル。読み取り専用(v0 はコピーです) | — | 作業ディレクトリ内でデータファイルをスキャンし、拡張子から <format> と区切り文字/エンコーディング/ヘッダー/引用符を検出します |
<contract> | 列ごとの推定契約、列間ルール、ガードレール。「正しさ」の唯一の基準です | — | 元のアーティファクトのプロファイルを作成し、合成して(以下を参照)、確認します |
<retention_floor> | 累積 unique_rows_kept / unique_rows_in_v0 がこの値以上である必要があります(分母は重複排除済み v0) | 0.95 | — |
<protected_columns> | 保持が必須の列。削除するには明示的な許可が必要です | すべて(*) | — |
<impute_cap> | ある列のセルのうち、補完(合成値を代入)できる最大割合 | 0.20 | — |
<analysis_cmd> | ユーザーの環境でプロファイリング/変換コードを実行するインタープリター(pandas が必要です) | python3 | 作業ディレクトリ内の pyproject.toml/.venv/uv |
<sandbox_root> | tcl/(バージョン、変換、プロファイル、台帳、パイプライン)の保存場所 | ./sandbox | — |
<gate> / <budget> | セーフティーネットのみ:iterations/tokens/time とその上限 | iterations / 30 | — |
プロファイリングと変換のコードは、<analysis_cmd> を使用してユーザーの環境で実行されるため、pandas を使用できます。ヘルパーコードは標準ライブラリを優先してください。負荷の大きいインポートは try/except ImportError で試し、利用できない場合は機能を縮退させるか、同意を得たうえで uv pip install "pandas==<ver>" を実行します。pandas が存在すると決めつけてはいけません。
コントラクトの合成(成否を分けるステップです。省略しないでください)
生のアーティファクトをプロファイリングし、観察した内容を2つのパートに分けてコントラクトにまとめます。
パート1 — 構造。 正しく解析されたこと(区切り文字/エンコーディング/ヘッダー)を確認します。単一の整然としたテーブル(1列に1変数、1セルに1つの値、1行に1つの観測値。セル結合、複数のサブテーブルの積み重ね、複数値セルがないこと)であり、列名/データ型が妥当であることを確認します。テーブルが誤って解析されていると、すべての列チェックが無意味になります。構造上の欠陥も変換/チェックの対象にします。
パート2 — 列ごとのコントラクト+ルール+ガードレール。 各列の意味的な型と正規形を判断し、次を出力します。データ型、NULL 許容か、キーか、範囲(数値の最小値/最大値)、正規表現(形式)、正規カテゴリ+merge_map(異表記 → 正規形。修正方法の指針であり、チェック時の置換ではありません)、重大度(キー/必須列はデフォルトでhigh、それ以外はnormal)、および任意の外れ値検出方法。データから根拠が得られる列間ルール(例:start ≤ end)も追加します。その後、前述のガードレール(retention_floor、protected_columns、impute_cap)を設定します。信頼性を保つためのルールは3つです。確約 — 観察されたすべての異常はチェックに変換するか、理由を明記して明示的に免除します(同じ値に複数の表記がある場合、自由記述のままにせず、必ずcategoriesチェックを設定します)。正規形が保存値 — 正規形の集合を宣言すると、ループが書き換えによって解消する未解決違反が生じます。厳格さを優先 — 判断に迷ったら、より厳しいチェックを追加します(厳しすぎる設定は簡単に戻せますが、誤ったチェックはチェック漏れより悪影響が大きいため、データから根拠が得られない列の意味を推測してはいけません)。
推定したコントラクトをユーザーに提示し、確認を得ます(Claude Code の場合:AskUserQuestionで簡潔なテーブルとして提示します。それ以外の場合:YAML を出力し、確認または修正を依頼します)。承認後、コントラクトとコンパイル済みチェックリストを<sandbox_root>/tcl/schema.yamlに書き込みます。実行中、コントラクトは固定されます。コントラクトの完全な形式はexamples/run.example.yamlを参照してください。
チェックリスト
各コントラクトルールは、違反件数と状態を報告する1つの二値チェックにコンパイルされます。重み付きの小数値もイプシロンもありません。この単純な事実によって、分母に関する曖昧さがすべてなくなります。
| チェック ID(パターン) | ディメンション | 次の場合に違反としてカウント |
|---|---|---|
<col>.required | 完全性 | nullable:falseのセルが NULL |
<col>.type | 妥当性 | NULL ではないセルをコントラクトのデータ型として解析できない |
<col>.range | 妥当性 | NULL ではない数値セルが[min,max]の範囲外 |
<col>.regex | 妥当性 | NULL ではないセルが形式の正規表現に一致しない |
<col>.categories | 一貫性 | NULL ではない保存値が正規カテゴリに含まれない |
<rule_id>(列間) | 一貫性 | 行が列間ルールに違反している |
<col>.key | 一意性 | 宣言されたキーの値が重複している |
rows.unique | 一意性 | 行が完全に重複している |
<col>.outlier(オプトイン) | 妥当性 | 数値セルが統計的な外れ値である(方法が宣言されている場合のみ) |
チェック状態: pass(違反0件)・open(違反が残っており、まだ解消に取り組める)・residual(違反は残っているものの、ガードレールを守りつつ別のチェックを悪化させずに修正できない状態。許容された行き止まりとして報告されます)。NULL が違反となるのは.requiredのみです。.type/.range/.regexチェックでは NULL を無視します。未解決チェックの優先順位: まずseverityが最も高いチェック、次に違反件数が最も多いチェックです。見出しのchecks_passing% = checks_in_pass / total_checksはreport.mdとステータス行にのみ使用し、保持/差し戻しの判断には決して使用しません。
正直な限界: チェックリストが測るのは形式の妥当性と内部整合性であり、正確さそのものではありません(「John Smith」が正しい名前かどうかなど)。外れ値/範囲チェックは、妥当性を推し量るための代替指標です。
ループ
<best> を現在の採用済みバージョンとします(v0、つまり <artifact> の完全なコピーから開始します)。
このチェックリストをコピーして項目にチェックを付けながら、停止条件が成立するまで <best> を反復処理します。
-
<best>のプロファイリング — 決定論的なプロファイリングコードを実行し、profiles/profile-vN.json(以下のスキーマ)に出力します。列ごとの統計、すべてのチェックの違反件数と状態、retention、見出しとなるchecks_passing%を含めます。4~8 行の概要を人が読める形で記述します。 - 対象を選び、変換を1つ提案する —
open状態のチェックから、優先度(重大度が高いもの、次に違反件数が多いもの)に従って選びます。チェック ID、違反件数、修復戦略、期待される効果を明記します。特定のopenチェックに結び付けられない変換は提案しないでください。 - 変換を記述する — 純粋な
transform(df) -> dfpandas 関数として記述します。対象チェックに必要な箇所だけを決定論的に変更し、後でpipeline.py内で単独実行できるようにします。 - 適用 → 候補版 —
<best>を読み込み、変換を適用してversions/vN+1.<ext>に書き込みます。エラーが発生した場合は1回修正します。それでも失敗する場合はstatus=crashと記録して破棄し、<best>は更新しません。 - 候補版を再プロファイリングする —
vN+1でチェックリストを再実行し、新しい違反件数とretentionを確認します。 - 採用または差し戻し(チェックリストのロジックのみ、イプシロンなし) — 対象チェックの違反件数が厳密に減少し、ほかのチェックの違反件数が増加せず、かつガードレールに抵触していない場合にのみ採用します。それ以外は差し戻します。それが対象チェックに対する最後のガードレール内で安全な戦略だった場合は、そのチェックを
residualとします。 - 記録して保存する —
ledger.tsvに1行追加します。採用した場合は、関数をtransforms/tNN_<slug>.pyにコピーし、その呼び出しを順序どおりpipeline.pyに追加します。 - 停止条件を確認する — どの停止条件も成立していなければ、新しい
<best>で次の反復に進みます。
破壊を最小限にする原則(このループの基本方針): 削除よりも修復を優先します。ガードレール内で安全かつ回帰が発生しないものを、次の順に試して採用します。(a) 修復(値をパース/標準化/正規化する)、(b) 補完(<impute_cap> の範囲内で、合成値としてフラグ付けする。nullable:true 列内の復元不能な値を null にするのは修復であり、補完ではないため、上限の対象外です)、(c) 削除(保持率と保護対象の下限を守って行/列を削除する)。削除は最後の手段です。チェックを residual とするのは、3つすべてが実際に失敗した場合に限ります。たとえば、nullable:false 列の不正な値を修復で復元できず、削除すると保持率に違反し、null にするとその列の .required チェックで回帰が発生する場合です。根拠のある residual は適切です。チェックをクリアするためだけに必須フィールドを null にしたり、値を捏造したりしないでください。
停止条件
チェックは決定論的な件数に基づくため、「完了」は厳密です。イプシロンも、停滞を判定するヒューリスティックもありません。 ここが autoresearch ループとの重要な違いです。終了するよう設計されています。 次のいずれかの条件が成立したら停止します。
- すべて正常 — すべてのチェックが
passです。クリーンな成功終了です。 - すべて residual —
open状態のチェックが残っていません(すべてのチェックがpassまたはresidual)。残りの違反は、ガードレールの範囲内では修正できないことが証明されています。自然な終了です。 - 予算 —
<gate>/<budget>に到達しました。最後の保護策にすぎません。
最後の open チェックを residual にする前に(これにより停止条件 #2 が成立します)、3つすべての戦略(修復 → 補完 → 削除)を実際に試したことを確認してください。最初の戦略で別のチェックに回帰が発生したり、下限に達したりしただけで、修正不能と判断してはいけません。
停止時: <best> を cleaned.<ext> に設定し、report.md を書き込み、最終概要(見出しとなる checks_passing%、pass/residual の件数、成立した停止条件)を出力します。
出力と形式
実行によって、3 つの成果物が生成されます。
cleaned.<ext>— 最良のバージョン(<best>のコピー)。pipeline.py— 単独で再実行できるスクリプトです。生の<artifact>を読み込み、承認された各変換を順に適用し、クリーニング済みのデータを書き出します。決定論的かつ冪等で、再実行すると生データのダンプからcleaned.<ext>が完全に再現されます。監査用の成果物です。ledger.tsv+report.md— 監査証跡の全体と、変更前後の概要です。
<sandbox_root>/tcl/の構成:schema.yaml(バインディング+契約+コンパイル済みチェックリスト)、versions/(v0.<ext>は生の成果物の読み取り専用コピーで、新しい候補ごとに新しいvNを書き込みます)、transforms/(承認された変換ごとに1ファイル)、profiles/、ledger.tsv、pipeline.py、report.md。
ledger.tsv — タブ区切りの追記専用ファイルで、試行ごとに1行です。自由記述にカンマは使用しません。schemas/ledger.schema.jsonに従う1行で、statusはkeep、revert、residual、crashのいずれかです。
iter transform_id target_check dimension viol_before viol_after regressions retention status rows_affected cells_affected summary
1 t01_drop_dupes rows.unique uniqueness 7 0 0 1.000 keep 7 0 remove 7 exact duplicate rows
2 t02_canon_status status.categories consistency 142 0 0 1.000 keep 0 142 rewrite variants to canonical set
3 - contact.regex validity 3 3 - 0.94 residual - - repair impossible; drop breaches retention; null regresses contact.required
profiles/profile-vN.json — schemas/profile.schema.jsonに従います(列ごとの統計、各チェックの件数と状態、保持率、見出し)。コンパクトなインスタンス:
{"version":"v2","rows":980,"cols":7,"checks_total":11,"checks_passing":9,
"checks_passing_pct":0.82,"total_violations":3,"retention":0.98,
"checks":[{"id":"status.categories","dimension":"consistency","scope":"status",
"severity":"normal","violations":0,"state":"pass"}],
"columns":[{"name":"status","dtype":"object","contract_dtype":"category",
"pct_null":0.0,"n_unique":5}],
"summary":"status canonicalized; one regex check residual on contact."}
report.md — v0と最終版の見出しchecks_passing%、開始時と終了時の違反件数を含む完全なチェックリスト、確認済みの契約、順番に並べた承認済み変換(パイプライン)、残存項目(理由付き)、削除または補完した内容、および発動した停止条件です。
制約
- 生の
<artifact>は変更しません — すべての作業は<sandbox_root>/tcl/内で行います。v0はコピーであり、生のダンプが再実行時の正解となるため、ループでは新しいvNバージョンのみを書き込みます。 - 行の追加、キー/インデックスの捏造、値の無断創作は禁止です — 補完は許可されますが、合成データとして明示し、記録したうえで、列ごとに
<impute_cap>を上限とします。 - ガードレールを守ってください — 候補が保持率の下限に違反する、保護対象の列を削除する、または補完上限を超える場合は、どれだけ多くのチェックを通過していても元に戻します。そうしないと、削除によってチェックリストを不正に操作できるためです。
- 各チェックは決定論的な件数であり、LLMによる判定ではありません。何が「正しい」かを決める唯一の基準は確認済みの契約です。実行中に契約やコンパイル済みチェックリストを無断で変更しないでください。
- 保持/復元の判断は純粋なチェックリストのロジックに従います — 対象チェックが改善し、後退がなく、ガードレールにも違反しない場合に限り保持します。イプシロンや、判断を左右する重み付きスコアは使用しません。
- 反復ごとに変換は1つだけとします。1つの未解決チェックに結び付いた純粋なpandas関数を使い、各違反数の増減を特定できるようにします。複数段階の一括書き換えは禁止です。
- ガードレールの範囲内で修正できないチェックは
residualとなり、報告されます。必須データの捏造やNULL化によって、無理に解消してはなりません。 pipeline.pyの忠実性を保ってください — 承認済みの変換だけを、承認された順序どおりに実行します。生のダンプに対して再実行した場合、cleaned.<ext>が生成されなければなりません。- 未解決チェックが残っている間に、続行するかどうかを尋ねるために停止しないでください。人間が確認するのはセットアップ時のみで、ループは停止条件に達するまで実行します。許可を得て固定バージョンをインストールする場合を除き、環境にないパッケージをインストールしてはなりません。また、
tcl/をgitにコミットしないでください。