Community研究&データ分析github.com

dq-parser — atomizace dat

データのアトミック化(複合値を専用列に分割)を行います。住所、氏名、日付などのフィールドをパースし、標準化・補完・重複除去の前処理として機能します。

dq-parser — atomizace dat とは?

このスキルは、データ品質監査後の最初の remediation ステップであるパーサー(dq-parser)を提供します。主な機能は、複合値の分割(例:住所の「通り名+番地」を別列に分離)、氏名のミドルネームや敬称の抽出、日付テキストのフォーマット判定などです。決定論的ルールベースで動作し、曖昧なケースでは NULL を返してエラーテーブルに記録するため、データの静かな破壊を防ぎます。SQL(MySQL 8)と Python(pandas)の両方に対応しており、Claude Code, Cursor, Codex などの AI エージェントから直接利用できます。出力は新しい `_STD` カラムとして元のデータを保持しつつ、パース成功率のメトリクスとエラーテーブルを生成するため、データ品質改善プロセス全体の透明性を高めます。

対応✓Claude Code~Codex CLI~Cursor
npx skills add koprjaa/dq-skills

Installed? Explore more 研究&データ分析 skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

パーサー — アトミック化

最初の remediation 手順。アトミックでない限り、何も標準化してはいけません。 ADDR_STREET フィールドに「Na Budíně 854」が含まれている場合、これを標準化しても意味がありません — 住所レジストリは通り名と番地を別々に認識します。

パイプライン: 監査 → dq-parser → dq-standardizator → dq-adresar → dq-imputator → dq-deduplikator

ルール

  • 元データは削除しない。 パース結果は新しいカラム(_STD, _NUM1, _NUM2)に出力します。 元データは before/after 比較のための証拠であり、パーサーが誤った場合のセーフティネットです。
  • 決定論的、ヒューリスティックではない。 3% のケースで誤った推測をするパーサーは、静かなデータ破壊を引き起こします。 ルールに確信が持てない場合は NULL を返し、その数をカウントします。 これは既知のパターンを持つ構造化フィールド(住所、氏名、日付)に適用されます。 パターンのない自由文テキストに対しては、統計的エンティティ認識(NER, spaCy)は正当なツールですが、 その出力は推定値であり、信頼度スコアとフラグが付随し、レビューなしにプロダクションカラムには移せません。
  • パースの割合を測定する — 分解できた行数を記録します。これはレポートの指標です。
  • パースは他のすべての前処理より先に行います。標準化、補完、重複除去はすべてアトミックなフィールドに依存します。

住所

最も一般的な欠陥: 番地用の専用カラムが 100% 空で、番地が通り名に混在している。 ソースでは Na Budíně 854, Nádražní 1211/25a, Bezručova 12/3 のようになります。

三つのコンポーネントに分解 — 通り名、家屋番号、方向番号(文字を含む場合あり):

-- MySQL 8: REGEXP_SUBSTR / REGEXP_REPLACE
UPDATE PARTY_ADDRESS SET
  ADDR_NUM1_STD   = REGEXP_SUBSTR(ADDR_STREET, '[0-9]+(?=(/[0-9]+[a-zA-Z]?)?[[:space:]]*$)'),
  ADDR_NUM2_STD   = REGEXP_SUBSTR(ADDR_STREET, '(?<=/)[0-9]+[a-zA-Z]?[[:space:]]*$'),
  ADDR_STREET_STD = TRIM(REGEXP_REPLACE(ADDR_STREET, '[0-9]+(/[0-9]+[a-zA-Z]?)?[[:space:]]*$', ''));

Python 版(pandas パイプラインの場合):

num  = raw.str.extract(r'(\d+)(?:/\d+\w?)?\s*$', expand=False)      # 家屋番号
street = raw.str.replace(r'\d+(/\d+\w?)?\s*$', '', regex=True).str.strip()

数値の順序に注意: チェコ共和国では 家屋番号/方向番号 と記述されますが、 この規則が常に守られているわけではありません。 パーサーが一つの数値しか見つけられなかった場合は、家屋番号として保存し、方向番号は NULL のままにします — 推測しないでください。

市区町村フィールドの複合値

Praha 5 - Smíchov, České Budějovice - České Budějovice 5 — オペレーターが一つのフィールドに 住所階層全体(市区町村 + 行政区画 + 地区番号)を書き込んだものです。これにより、レジストリとの照合が不可能になります。

-- 最初のハイフン前 = 市区町村、残り = 行政区画
SELECT ADDR_CITY,
  TRIM(SUBSTRING_INDEX(ADDR_CITY, '-', 1))                      obec,
  NULLIF(TRIM(SUBSTRING(ADDR_CITY, LOCATE('-',ADDR_CITY)+1)),'') mcast
FROM PARTY_ADDRESS WHERE ADDR_CITY LIKE '%-%';

パース開始前の候補検出:

SELECT ADDR_CITY, COUNT(*) cnt FROM PARTY_ADDRESS
WHERE ADDR_CITY REGEXP '[0-9]' OR ADDR_CITY LIKE '%-%' OR LENGTH(ADDR_CITY) > 50
GROUP BY 1 ORDER BY cnt DESC LIMIT 20;

注意: 市区町村名のハイフンは正当な場合もあります(Frýdek-Místek, Brandýs nad Labem-Stará Boleslav)。そのため、分解は市区町村レジストリに対して検証され、盲目的に行われません — そして、分解後に市区町村がレジストリに存在しない場合は、元の値を返します。

P.O. Box およびその他の範囲外の形式

P.O. Box、私書箱、「会社の配達住所」 — 形式的には有効な住所ですが、 建物レジストリには存在しません。これらを無理にパースせず、専用フラグでマークし、 マッチ率の指標から除外します(そうしないと、エラーではないものをエラーとして報告します)。

氏名

埋め込みスペース

D A V I D, Ž A N N A — すべての文字がスペースで区切られています。 すべてのトークンが一文字の場合、または一文字のトークンが過半数(残りは通常ハイフンやイニシャル)である場合に、 決定論的に結合できます:

toks = s.split()
if len(toks) > 1 and all(len(t) == 1 for t in toks):
    s = "".join(toks)
elif len(toks) > 1 and sum(len(t) == 1 for t in toks) >= max(2, len(toks) - 1):
    s = "".join(toks)

トークンを一般的に結合しないでください — Anna Marie は二つの名前であり、AnnaMarie ではありません。

複数部分からなる名前

名 + ミドルネームが一つのフィールドに含まれています。スペースで分割し、二番目のトークンを別に保存します。 一文字のトークン(イニシャル)は破棄します。そうしないと無意味なミドルネームが生成されます:

UPDATE PART_PARTY
SET PARTY_FNAME_STD = <proper>(SUBSTRING_INDEX(PARTY_FNAME,' ',1)),
    PARTY_MNAME_STD = NULLIF(<proper>(SUBSTRING_INDEX(SUBSTRING_INDEX(PARTY_FNAME,' ',2),' ',-1)),
                             <proper>(SUBSTRING_INDEX(PARTY_FNAME,' ',1)))
WHERE PARTY_TYPE='P' AND LENGTH(SUBSTRING_INDEX(PARTY_FNAME,' ',2)) > 1;

名前フィールドに含まれる敬称

PARTY_NAME 内の Ing. Jan Novák。ピリオドではなく、最初のトークンが敬称辞書と一致するかで検出します — J. Novák はイニシャルであり、敬称ではありません。

辞書は敬称コードリスト(REF_*_TITBEF は名前の前の敬称、TITAFT は名前の後)であり、 MuDr, MUDr., Mudr のようにデータ内で異なる表記が混在するため、ファジー一致を使用します。 パーサーは敬称を分離するだけです。正規形への変換は dq-standardizator の仕事です。

エンコーディングエラー

二つの異なる問題、二つの異なる解決策:

エラー例解決策
系統的な文字の置き換えĄUDOVÍT (正しくは ĽUDOVÍT)パターンが一意であれば決定論的置換 — 例:単語の先頭の Ą は常に誤った書き換えであり、ポーランド語では決して出現しない
欠落文字BE0ŠOVÁ (正しくは BENEŠOVÁ)パーサーでは修正しない。 _CORRUPT フラグを付け、修復は dq-standardizator(氏名レジストリ)または dq-deduplikator(クラスター内のクリーンな二重行)に任せる
s = re.sub(r"(^|(?<=\s))Ą", "Ľ", s)          # 一意、決定論的
corrupt = bool(re.search(r"[0-9]", s))       # 名に数字 = 破損、別の場所で処理

この分割は意図的です。パーサーは確実なことだけを行います。欠落文字の再構築は推定であり、 検証可能な場所で処理します。

テキストとして保存された日付

パースする前に、フォーマットと日/月を確認します:

SELECT SUBSTRING(<col>,1,2) prvni, SUBSTRING(<col>,4,2) druhy, COUNT(*)
FROM <t> GROUP BY 1,2 ORDER BY 3 DESC LIMIT 20;

最初の位置が決して 12 を超えず、二番目の位置が超える場合、順序は MM.DD です。 両方が 12 を超える場合、フォーマットは混合しており、第二のソースに基づいて行ごとに判断する必要があります。 盲目的な STR_TO_DATE は静かなスワップを検出できず、テーブル全体に広げてしまいます。

パーサーの出力

  • アトミックな値を持つ新しい _STD カラム、元データはそのまま。
  • 各コンポーネントのパース率(空でない結果の行の %)。
  • エラーテーブル: パーサーが分解できなかった行と、各行の理由 — 行が通過しなかった理由の監査証跡。 手動レビューまたは「これは範囲外」と判断するための入力。

関連スキル