平均が二つある — 「なし」と「同じ」を規則で決める
目標
なしの表記を種類別に数え、欠損を0で埋めたときに平均がどう変わるかを数字で示し、完全重複とキー重複と人の目にだけ同じものを分けて、正規化・ブロッキング・判定ルールで同じ人をまとめるツールnulls.pyを作ります。
なぜ重要なのか
同じファイルを見て、平均が2つ出ることはよくあります。片方は値がない行を除いて数え、もう片方は0で埋めて数えたからです。どちらもエラーを出さないので、誰も気づきません。そのため、平均を言うときは分母も一緒に言う必要があり、そのためには、表記ごとの件数を先に数えなければなりません。 0となしは別の事実です。0ウォンの顧客は「まだ買っていない」で、空欄の顧客は「わからない」です。ファイルに0と書かれていると、どちらなのかをファイルだけでは判断できないので、件数を別に数えて報告し、業務に尋ねます。 重複も1種類ではありません。1文字まで同じ行は1つだけ残せば足りますが、同じ識別子で内容が違う行は自動では直せず、表記だけが違う同じ対象は、ルールで判定する必要があります。ここで最もよくある事故は、正規化した値が同じになったからといってマージしてしまうことです。 採点ツールは、提出された文言を信じません。一時ディレクトリに、採点ツールが作った名簿を用意し、作成したツールを実際に実行して、件数・平均・まとめられたグループを、採点ツールが自分で数えた値と照合します。名前と金額は、実行ごとに変わります。
ステップ
- /root/miss/gen_customers.pyを作成して実行し、customers.csvを作成してください(保存先: /root/miss/raw/customers.csv)。欠損の表記と、3種類の重複が入っています。
- /root/miss/nulls.pyに
census <파일> <칼럼>を作成し(プレースホルダーはファイルとカラムです)、なしの表記を種類別に数えさせてください。 stats <파일> <칼럼>を追加して(プレースホルダーはファイルとカラムです)、欠損を0で埋めた平均と、除いて出した平均と、0まで除いた平均を並べて出力させ、結果を書いてください(保存先: /root/miss/stats.json)。aging <파일>を追加して(プレースホルダーはファイルです)、センチネル日付が平均処理期間をどう壊すかを測り、結果を書いてください(保存先: /root/miss/aging.json)。dupes <파일>を追加して(プレースホルダーはファイルです)、完全重複とキー重複を分けて数えさせてください。blocks <파일>を追加して(プレースホルダーはファイルです)、正規化したあとに電話番号の下4桁でブロックに分け、比べる候補の組がいくつに減ったかを出力させてください。match <파일>を追加して(プレースホルダーはファイルです)、判定ルールで同じ人をまとめ、ブロッキングが見逃した組まで測らせてください。ルールは、/root/miss/rules.jsonに書きます。- 全体を一度に処理して、/root/miss/miss_report.jsonと、/root/miss/miss_report.mdを作成してください。
参考
- 実行契約:
python3 /root/miss/nulls.py <명령> <파일> [칼럼](プレースホルダーは順に、コマンド、ファイル、カラムです)。censusとstatsは、カラム名をもう1つ受け取り、aging・dupes・blocks・matchは、ファイルだけを受け取ります。成功すれば終了コード0、ファイルがなければ3、コマンドや引数の数が間違っていれば2です。 - なしとみなす表記は、空欄と
NULL・NA・N/A・NONE・-・?で、大文字小文字は区別しません。センチネル日付は9999-12-31です。0はなしとして数えず、別に数えます。本物の0なのか埋めた0なのかは、ファイルだけを見てもわからないからです。この一覧はこのラボの前提です。 censusの応答:{"column": 이름, "total": 정수, "blank": 정수, "tokens": {"NULL": 정수, ...}, "zero": 정수, "sentinel": 정수, "present": 정수}(プレースホルダーは名前と整数です)。tokensのキーは大文字にそろえます。presentは、totalからblank・sentinel・tokensの合計を引いた値です。statsの応答:column・n_all・sum_naive・mean_naive・n_known・sum_known・mean_known・n_nonzero・mean_nonzero・gap。平均は小数第2位で四捨五入します。mean_naiveは欠損を0で埋めたものなので、分母がn_allで、mean_knownは、分母がn_knownです。gapは、mean_knownからmean_naiveを引いた値です。agingの応答:n_all・n_closed・open_count・unknown・avg_days_naive・avg_days_closed。avg_days_naiveは、センチネル日付をそのまま日付として受け入れて出した平均で、avg_days_closedは、センチネル日付を除いた平均です。開始日か終了日がなしなら、unknownとして数えます。dupesの応答:rows・exact・key_dupes・keys。exactは、同じ行が複数回出てきた超過分の件数で、key_dupesは、record_idが2回以上出てきたキーの個数です。- 正規化ルール: 名前は、前後の空白を除去し、法人の表記(
(주)・㈜・(유)・주식회사・유한회사。韓国語の法人表記です)を除去し、空白を1つにまとめ、小文字にします。メールアドレスは、前後の空白を取り除いて小文字にします。電話番号は、数字だけを残します。 - ブロックキーは、正規化した電話番号の下4桁で、4桁に満たなければ
NONEです。blocksの応答:rows・blocks・candidate_pairs・pairs_without_blocking。 - 判定ルール: 正規化したメールアドレスが空でなく同じなら同じ人、または、正規化した名前と電話番号が両方とも空でなく同じなら同じ人です。
matchの応答:rows・clusters・merged・groups・recall_loss。groupsは、2行以上まとまった集団だけを入れ、各集団は、ヘッダーを除いた行番号(1から)を昇順に入れた配列です。recall_lossは、総当たりの比較で見つけた組の数から、ブロックの中で見つけた組の数を引いた値です。 - 公式ドキュメント: python csv・python statistics・python datetime・python collections
- よくあるミス: 欠損を0で埋めて分母は全体のままにすること、センチネル日付を日付として計算すること、メールアドレスが空の行どうしをまとめること、正規化した値が同じになったからとマージすること、ブロッキングの損失を測らないこと。
3つのシステムから統合した名簿を作る
/root/miss/gen_customers.pyを作成して実行し、customers.csvを作成してください(保存先: /root/miss/raw/customers.csv)。なしの表記5種類と、完全重複・キー重複・表記だけが違う同じ人が一緒に入ります。
まず人を36人作り、そのうち12人を、社名の表記と電話番号の書式だけを変えて、もう一度入れます。そのあと、前の行をそのままコピーした完全重複と、record_idだけが重なって人は別の行を加えます。金額と終了日には、空欄・NULL・NA・ハイフン・0・センチネル日付を混ぜます。
なしの表記を種類別に数える
/root/miss/nulls.pyにcensus <파일> <칼럼>を作成し(プレースホルダーはファイルとカラムです)、total・blank・tokens・zero・sentinel・presentを出力させてください。0はなしとして数えず、別に数えます。
値の前後の空白だけを取り除いて大文字にそろえ、一覧と比べます。0をなしに入れない理由が、このステップの核心です。本物の0なのか埋めた0なのかはファイルだけを見てもわからないので、数えておいて業務に尋ねるのが正しいです。
0で埋めた平均と除いて出した平均
stats <파일> <칼럼>を追加し(プレースホルダーはファイルとカラムです)、3種類の平均を並べて出力させ、amountカラムに対する結果を保存してください(保存先: /root/miss/stats.json)。
3つの平均の違いは分母です。欠損を0で埋めると、合計はそのままで分母だけが大きくなり、平均が下がります。0まで除いた平均は、分母がさらに小さくなって平均が上がります。3つの数字はすべて正しく、間違っているのは、何を数えたかを言わないレポートです。
9999-12-31が作った数千年
aging <파일>を追加し(プレースホルダーはファイルです)、センチネル日付をそのまま日付として受け入れた平均と、除いて出した平均を一緒に出力させ、結果を保存してください(保存先: /root/miss/aging.json)。
9999-12-31は日付ではなく、「まだ終わっていない」という印です。それを日付として計算すると、平均が数千年になります。滑稽に見えますが、数件が混ざるだけで平均が静かに膨らみ、目立ちません。開始日か終了日がなしなら、計算から除いてunknownとして数えます。
完全重複とキー重複を分ける
dupes <파일>を追加して(プレースホルダーはファイルです)、rows・exact・key_dupes・keysを出力させてください。exactは、同じ行が複数回出てきた超過分で、key_dupesは、record_idが2回以上出てきたキーの個数です。
2種類を分けるのは、処理方針が違うからです。1文字まで同じ行は、判断することなく1つだけ残せば足りますが、同じ識別子で内容が違う行は、どちらが正しいかを機械が知りません。そうしたキーは、一覧として残して業務に渡す必要があります。
正規化してブロッキングで候補を減らす
blocks <파일>を追加して(プレースホルダーはファイルです)、rows・blocks・candidate_pairs・pairs_without_blockingを出力させてください。ブロックのキーは、正規化した電話番号の下4桁で、4桁に満たなければNONEです。
すべてを組み合わせて比べると、行数の2乗に比例して増えます。安いキーで先に分ければ、比べる組が大きく減ります。正規化は比較のためのもので、このステップで何かをまとめるものではありません。まとめる仕事は、次のステップのルールが行います。
同じ人をまとめることと、ブロッキングが見逃した組
match <파일>を追加して(プレースホルダーはファイルです)、rows・clusters・merged・groups・recall_lossを出力させてください。ルールは、/root/miss/rules.jsonに、block_key・normalize・match・not_matchとして書きます。
メールアドレスが空の行どうしを「メールアドレスが同じ」としてまとめると、互いに別の顧客数十人が1人になります。ルールに「空でなく」を必ず入れてください。recall_lossは、総当たりの比較で見つけた組から、ブロックの中で見つけた組を引いた値です。ブロッキングはただではないという事実を、数字で残す場所です。
欠損と重複を1枚で報告する
全体を一度に処理して、rows・missing・mean_naive・mean_known・exact・key_dupes・clusters・merged・recall_lossを書き(保存先: /root/miss/miss_report.json)、/root/miss/miss_report.mdに、## 없음은 몇 가지로 적혀 있었나、## 0 으로 채우면 무엇이 달라지나、## 무엇을 같은 것으로 봤나、## 남은 위험の4つの節で書いてください(韓国語の見出しは順に「なしはいくつの形で書かれていたか」「0で埋めると何が変わるか」「何を同じものとみなしたか」「残るリスク」という意味です)。
missingは、カラム名をキーにして、censusの結果を入れたオブジェクトです。amountとclosed_atの2つのカラムを入れてください。レポートには、3つの平均を分母とともに書き、ブロッキングが見逃した組の数を、残るリスクに書いてください。その組は、誰も報告しません。