고객 데이터 다루기 · 없음과 같음 · 실습
평균이 두 개다 — 없음과 같음을 규칙으로
목표
없음의 표기를 종류별로 세고, 결측을 0 으로 채웠을 때 평균이 어떻게 달라지는지를 숫자로 보이고, 완전 중복과 키 중복과 사람 눈에만 같은 것을 갈라 정규화·블로킹·판정 규칙으로 같은 사람을 묶는 도구 nulls.py 를 만든다.
왜 중요한가
같은 파일을 보고도 평균이 두 개 나오는 일은 흔하다. 한쪽은 값이 없는 행을 빼고 셌고 다른 쪽은 0 으로 채워 셌기 때문이다. 둘 다 오류를 내지 않아서 아무도 알아채지 못한다. 그래서 평균을 말할 때는 분모를 함께 말해야 하고, 그러려면 표기별 건수를 먼저 세야 한다.
0 과 없음은 다른 사실이다. 0 원인 고객은 "아직 안 샀다" 이고 빈 칸인 고객은 "모른다" 이다. 파일에 0 이라고 적혀 있으면 둘 중 어느 쪽인지 파일만 봐서는 알 수 없으므로, 건수를 따로 세어 보고하고 업무에 묻는다.
중복도 한 가지가 아니다. 글자까지 같은 줄은 하나만 남기면 되지만, 같은 식별자에 내용이 다른 줄은 자동으로 고칠 수 없고, 표기만 다른 같은 대상은 규칙으로 판정해야 한다. 여기서 가장 흔한 사고는 정규화한 값이 같아졌다는 이유로 병합해 버리는 것이다.
채점기는 여러분의 문구를 믿지 않는다. 임시 디렉터리에 채점기가 만든 명부를 차려 놓고 여러분의 도구를 실제로 실행해 건수·평균·묶인 그룹을 채점기가 직접 센 값과 대조한다. 이름과 금액은 실행마다 바뀐다.
단계
1. /root/miss/gen_customers.py 를 만들어 실행해 /root/miss/raw/customers.csv 를 만드세요. 결측 표기와 세 종류의 중복이 들어 있습니다.
2. /root/miss/nulls.py 에 census <파일> <칼럼> 을 만들어 없음의 표기를 종류별로 세게 하세요.
3. stats <파일> <칼럼> 을 더해 결측을 0 으로 채운 평균과 빼고 낸 평균과 0 까지 뺀 평균을 나란히 내게 하고, 결과를 /root/miss/stats.json 에 적으세요.
4. aging <파일> 을 더해 보초 날짜가 평균 처리 기간을 어떻게 망가뜨리는지 재고, 결과를 /root/miss/aging.json 에 적으세요.
5. dupes <파일> 을 더해 완전 중복과 키 중복을 갈라 세게 하세요.
6. blocks <파일> 을 더해 정규화한 뒤 전화번호 뒤 네 자리로 블록을 나누고, 견줄 후보 쌍이 몇 개로 줄었는지 내게 하세요.
7. match <파일> 을 더해 판정 규칙으로 같은 사람을 묶고 블로킹이 놓친 쌍까지 재게 하세요. 규칙은 /root/miss/rules.json 에 적습니다.
8. 전체를 한 번에 처리해 /root/miss/miss_report.json 과 /root/miss/miss_report.md 를 만드세요.
참고
- 실행 계약:
python3 /root/miss/nulls.py <명령> <파일> [칼럼]. census 와 stats 는 칼럼 이름을 하나 더 받고, 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. 평균은 소수 둘째 자리에서 반올림합니다.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가 두 번 이상 나온 키의 개수입니다.- 정규화 규칙: 이름은 앞뒤 공백 제거 → 법인 표기(
(주)·㈜·(유)·주식회사·유한회사) 제거 → 공백 하나로 합치기 → 소문자. 이메일은 앞뒤 공백 제거 후 소문자. 전화번호는 숫자만 남깁니다. - 블록 키는 정규화한 전화번호의 뒤 네 자리이고, 네 자리가 안 되면
NONE입니다.blocks응답:rows·blocks·candidate_pairs·pairs_without_blocking. - 판정 규칙: 정규화한 이메일이 비어 있지 않고 같으면 같은 사람, 또는 정규화한 이름과 전화번호가 둘 다 비어 있지 않고 같으면 같은 사람입니다.
match응답:rows·clusters·merged·groups·recall_loss. groups 는 두 줄 이상 묶인 무리만 담고, 각 무리는 머리글을 뺀 행 번호(1부터) 를 오름차순으로 담은 배열입니다. recall_loss 는 전수 비교로 찾은 짝의 수에서 블록 안에서 찾은 짝의 수를 뺀 값입니다. - 공식 문서: [python csv](https://docs.python.org/3/library/csv.html) · [python statistics](https://docs.python.org/3/library/statistics.html) · [python datetime](https://docs.python.org/3/library/datetime.html) · [python collections](https://docs.python.org/3/library/collections.html)
- 흔한 실수: 결측을 0 으로 채우고 분모는 전체로 두기, 보초 날짜를 날짜로 계산하기, 이메일이 비어 있는 행끼리 묶기, 정규화한 값이 같아졌다고 병합하기, 블로킹의 손실을 안 재기.
단계 8개
- 세 시스템에서 합쳐 온 명부 만들기
- 없음의 표기를 종류별로 세기
- 0 으로 채운 평균과 빼고 낸 평균
- 9999-12-31 이 만든 수천 년
- 완전 중복과 키 중복 가르기
- 정규화하고 블로킹으로 후보 줄이기
- 같은 사람 묶기와 블로킹이 놓친 쌍
- 결측과 중복을 한 장으로 보고하기