결측률·중복·범위 검사
80분 안팎
학습 목표
JSON 행 목록의 결측률·중복 키·음수 통행량을 검사하고 오류 코드를 출력합니다.
개념
결측을 허용하는 표에도 품질 기준이 필요합니다
교통량 표에 숫자가 들어 있다고 바로 보고서를 만들면 안 됩니다. 공급자가 같은 지역·날짜를 두 번 보냈거나, 장비 장애로 빈칸이 늘었거나, 집계 과정에서 음수가 생겼을 수 있습니다. 앞 모듈의 멱등 저장은 같은 입력의 재실행을 안전하게 합니다. 그러나 잘못된 값을 한 번만 저장하는 것도 잘못입니다. 이번 레슨에서는 저장할 수 있는 형식과 분석에 사용할 수 있는 품질을 분리하여 판단합니다.
형식 검사는 날짜가 실제 달력 날짜인지, 통행량이 정수인지 확인합니다. 품질 검사는 그 형식이 맞더라도 지역·날짜 키가 중복되는지, 비어 있는 통행량이 얼마나 많은지, 값이 관측 계약에 맞는지 확인합니다. 두 검사는 서로를 대체하지 않습니다. 문자열을 숫자로 변환할 수 있다는 사실은 값의 범위나 표본의 대표성을 보장하지 않습니다. 검사 함수는 오류를 발견해 설명하고 공개 여부는 다음 레슨의 게이트가 결정합니다.
환경과 입력 계약
이 모듈의 로컬 실습은 Python 3.9 이상과 표준 라이브러리 csv, json, sqlite3, unittest를 사용합니다. ZIP을 풀고 루트에서 안내한 명령을 실행합니다. 외부 계정이나 네트워크 연결은 필요하지 않습니다. 자료는 실제 기관 실적이 아닌 교통·날씨 학습 표본입니다. 브라우저 Python은 표준 입력의 JSON 배열을 읽고 SQL은 테스트에서 생성한 SQLite 테이블을 조회합니다. 각 따라하기 코드는 독립 실행 예제입니다.
브라우저 검사기의 한 행은 region, date, total_vehicles, rain_mm을 가집니다. 지역과 날짜는 이미 정규화된 문자열이며 수치 필드는 숫자 또는 null입니다. 이번 문제는 날짜 파싱과 누락 필드 검사가 끝난 자료를 받는다고 가정합니다. JSON null은 Python에서 None입니다. 필드 자체가 없는 것과 필드 값이 null인 것은 다르므로 get으로 모두 None에 합치지 않습니다. 앞 모듈의 스키마 계약을 유지하고 품질 규칙만 추가합니다.
분모와 경계를 먼저 결정합니다
통행량 결측률은 total_vehicles가 None인 행 수를 입력 전체 행 수로 나눕니다. 강수 결측률도 같은 전체 행을 분모로 사용하지만 별도로 계산합니다. 완전한 행만 분모로 삼으면 결측 행을 분모에서 제거하여 실제 누락 정도를 왜곡합니다. 영점은 관측값이므로 결측으로 세지 않습니다. if not value라는 표현은 0과 None을 함께 참으로 처리하기 때문에 여기서는 value is None으로 검사합니다.
연습 기준은 통행량 결측률 0.2 초과와 강수 결측률 0.4 초과를 실패로 정합니다. 기존 여섯 행에 통행량 결측 한 개, 강수 결측 두 개가 있다는 표본을 유지하면서 누락 증가를 감지하려는 예시입니다. 허용률에 정확히 도달하면 통과합니다. 이 수치를 모든 공개 데이터에 적용하지 않습니다. 지역별로 누락이 집중되거나 비교할 쌍의 표본이 부족하면 전체 비율이 낮아도 분석 결론은 약해질 수 있습니다.
분모가 0인 빈 배열은 결측률 0으로 간주하지 않고 EMPTY만 출력합니다. 계산 전에 빈 입력을 처리하여 ZeroDivisionError를 막습니다. 데이터가 없다는 사실과 결측이 없다는 사실은 다른 상태입니다. 이번 게이트는 비어 있는 새 배치를 오류로 취급하지만 앞 모듈의 증분 적재기는 빈 배치를 아무 변경 없는 작업으로 허용했습니다. 업무 목적이 바뀌어 새 공개 경로에 더 엄격한 규칙을 추가한 것입니다.
중복 키와 범위는 다른 검사를 사용합니다
관측 키는 지역과 날짜의 쌍입니다. 같은 날짜의 A와 B는 중복이 아니며 같은 A의 다른 날짜도 중복이 아닙니다. 키 목록의 길이와 집합의 길이를 비교하면 같은 키가 다시 등장했는지 확인할 수 있습니다. 같은 값이 반복되어도 이번 규칙에서는 DUPLICATE_KEY를 반환합니다. 원본 수신 오류를 눈에 보이게 남기려는 정책입니다. 정제 후 중복이 제거된 결과만 검사하면 공급 단계의 문제가 사라집니다.
통행량은 하루 절대 합계이므로 음수는 NEGATIVE_TRAFFIC입니다. None은 아직 모르는 값이므로 음수와 비교하지 않습니다. 0은 실제 통행이 없는 관측일 수 있어 허용합니다. 강수량의 음수·무한값과 숫자 형식은 이전 스키마 검사 책임이며 이번 브라우저에는 그 검사를 통과한 입력만 줍니다. 코드가 검사하지 않는 범위를 명시하면 제한된 예제가 모든 품질 문제를 잡는다고 오해하지 않습니다.
검사는 첫 오류에서 중단하지 않고 정해진 순서로 코드를 모읍니다. 순서는 DUPLICATE_KEY, NEGATIVE_TRAFFIC, TRAFFIC_MISSING, RAIN_MISSING입니다. 같은 자료에 여러 문제가 있으면 각 코드를 한 번씩 출력합니다. 정상 입력은 OK, 빈 입력은 EMPTY입니다. 오류 수가 아니라 오류 종류를 나열하는 계약입니다. 입력 순서가 바뀌어도 판정은 같으며 결정적인 출력 덕분에 테스트와 사람의 검토가 같은 근거를 공유합니다.
예외 메시지를 숨기지 않습니다
KeyError: total_vehicles는 해당 필드가 없는 행을 받았다는 뜻입니다. 그 자리를 영점으로 채우면 계약 위반을 정상 관측처럼 만들게 됩니다. TypeError가 숫자 비교에서 발생하면 문자열 등 정제되지 않은 값이 들어왔는지 확인합니다. JSONDecodeError는 입력 JSON 문법이 깨졌다는 뜻으로 품질 기준 초과와 별개입니다. 이런 형식 오류를 억지로 OK로 바꾸기보다 이전 검증 단계로 되돌려 책임 경계를 확인합니다.
검사 결과를 보정값으로 바꾸지 않습니다. 음수에 절대값을 씌우거나 중복 키를 마지막 행으로 덮어쓰면 원인이 숨겨집니다. 장비 오류인지 정정본 충돌인지 확인한 뒤 승인된 수정 입력을 만들어 다시 검사합니다. 결측도 임의 평균으로 채워 통과시키지 않습니다. 채우기가 분석 목적에 맞는 경우라면 원래 결측 수, 채운 방식, 지표 변화가 별도 근거로 남아야 합니다. 현재 문제는 진단만 수행합니다.
경계 사례로 규칙을 확인합니다
정상 두 행만으로는 if not value 오류를 발견하기 어렵습니다. 영점만 있는 배열, 정확히 임계값인 배열, 기준을 조금 넘는 배열, 빈 배열을 각각 실행합니다. 중복은 같은 값 반복과 다른 값 반복을 함께 확인합니다. 여러 오류가 동시에 발생하는 표본도 필요합니다. 테스트의 기대 결과는 구현 코드를 따라 계산하지 않고 먼저 정한 계약에 따라 작성해야 잘못된 구현과 함께 틀리는 일을 줄일 수 있습니다.
실습에서는 검사 함수를 완성한 뒤 오류 코드가 어떤 관측 조건을 뜻하는지 설명합니다. 같은 자료를 행 순서만 바꾸어 다시 넣어도 결과가 유지되는지 확인합니다. 출력 문자열의 철자나 순서가 다르면 채점은 실패할 수 있으므로 프롬프트의 계약을 읽습니다. 더 읽기의 테스트 장은 fixture와 매개변수 테스트를 확장하는 자료입니다. 여기서는 품질 경계를 결정하고 그 결정을 코드와 작은 반례로 검증하는 데 집중합니다.
품질 통과는 인과관계나 대표성의 인증이 아닙니다. 통행량과 강수량이 둘 다 있다는 사실만으로 비 때문에 통행량이 줄었다고 말할 수 없습니다. 공휴일이나 지역 특성 같은 비교 조건은 분석 단계에서 검토합니다. 검사기는 명시한 데이터 계약의 위반을 찾는 도구이며, 어떤 질문에 사용할 수 있는지에 대한 판단까지 대신하지 않습니다. 이 구분을 보고서의 한계에 남기면 통과 표시를 과도하게 해석하는 일을 막을 수 있습니다.
따라하기
영점과 결측 구분
None만 결측으로 세면 실제 영점을 보존합니다.
values=[0,None,100,0,None]
print('rows',len(values))
print('missing',sum(x is None for x in values))
print('rate',sum(x is None for x in values)/len(values))실행 결과
rows 5 missing 2 rate 0.4
임계값 경계 확인
정확히 기준과 같은 값은 허용하고 초과를 차단합니다.
for missing,n in [(1,5),(2,5),(1,6)]:
rate=missing/n
print(missing,n,format(rate,'.3f'),'FAIL' if rate>0.2 else 'PASS')실행 결과
1 5 0.200 PASS 2 5 0.400 FAIL 1 6 0.167 PASS
키 중복과 음수 동시 진단
한 오류만 확인하고 끝내지 않는 예제입니다.
rows=[('A','2026-09-01',0),('A','2026-09-01',-1)]
print('duplicate',len(rows)!=len(set((r[0],r[1]) for r in rows)))
print('negative',any(r[2]<0 for r in rows))실행 결과
duplicate True negative True
확인 문제
실습
정규화된 네 필드의 JSON 배열을 읽어 품질 코드를 검사 순서 DUPLICATE_KEY, NEGATIVE_TRAFFIC, TRAFFIC_MISSING, RAIN_MISSING으로 줄마다 한 번 출력합니다. 통행 결측률은 0.2 초과, 강수 결측률은 0.4 초과일 때 오류입니다. 분모는 전체 행이며 영점은 결측이 아닙니다. 빈 배열은 EMPTY만, 문제 없는 배열은 OK만 출력합니다. 날짜·키 형식과 숫자 타입은 이미 검증되었다고 가정합니다.
모범 답안
import json,sys
rows=json.load(sys.stdin)
if not rows:
print('EMPTY')
else:
codes=[]
keys=[(r['region'],r['date']) for r in rows]
if len(keys)!=len(set(keys)):codes.append('DUPLICATE_KEY')
if any(r['total_vehicles'] is not None and r['total_vehicles']<0 for r in rows):codes.append('NEGATIVE_TRAFFIC')
if sum(r['total_vehicles'] is None for r in rows)/len(rows)>0.2:codes.append('TRAFFIC_MISSING')
if sum(r['rain_mm'] is None for r in rows)/len(rows)>0.4:codes.append('RAIN_MISSING')
print('\n'.join(codes) if codes else 'OK')
더 읽기
면접 질문
- 보고서의 수치와 원본 데이터가 다를 때 확인할 순서를 설명해 주시면 됩니다.