사전 결측치
개념

결측치

gabury1고친 사람 github-actions[bot]

결측치는 있어야 할 기록에서 빠진 값입니다. 설문에서 답하지 않은 문항이나 센서가 꺼진 동안의 측정값이 그렇습니다. 빠진 값을 모른 척 계산하면 평균과 합계가 소리 없이 틀어집니다. 그래서 분석을 시작하기 전에 무엇이 몇 개 빠졌는지부터 셉니다.

쉽고 빠른 이해

결측치는 데이터에서 비어 있는 칸입니다. 회원 여섯 명 가운데 두 명이 나이를 안 적었으면 그 두 칸이 결측치입니다.

빈칸을 어떻게 셀지 정하지 않으면 계산이 틀어집니다. 빈칸을 0으로 치면 평균 나이가 확 내려갑니다. 빈칸을 빼고 계산하면 적어 낸 넷만의 평균이 나옵니다. 안 적은 두 명이 나이 많은 사람들이었다면 이 평균도 틀립니다. 어느 쪽이든 오류 없이 숫자 하나가 나와서 틀린 줄을 모릅니다.

숫자만 받는 칸에는 빈칸을 -1 같은 올 리 없는 숫자로 적기도 합니다. 이런 숫자도 빈칸입니다.

  1. 빈칸이 몇 개인지 셉니다. -1 처럼 숫자로 적힌 빈칸도 셉니다
  2. 왜 비었는지 봅니다
  3. 까닭에 맞춰 다룹니다. 아무렇게나 빠졌으면 그 행을 버립니다. 다른 열 때문에 빠졌으면 그 열 값이 같은 행들의 값으로 채웁니다. 그 밖에는 빈 채로 두고 비었다는 표시를 따로 적습니다

빈칸을 다루는 데는 대가가 따릅니다. 버리면 남은 사람만의 평균이 됩니다. 채우면 없던 값을 지어 넣는 셈입니다. 그래서 어느 쪽을 골랐는지 결과 옆에 적어 둡니다.

상세

반 출석부에 한 학생의 칸만 비어 있습니다. 결석해서 비워 둔 것인지, 선생님이 표시를 잊은 것인지 칸만 봐서는 모릅니다. 이 칸을 결석으로 치느냐 출석으로 치느냐에 따라 그 달 출석 인원이 달라집니다.

결측치는 기록됐어야 할 값이 데이터에 없는 것입니다. 영어로는 missing value 입니다. 결측값이라고도 씁니다. 회원 테이블의 나이 열로 보면 이렇습니다.

회원 가입 경로 나이
1 웹 30
2 앱 20
3 앱 (빈칸)
4 웹 40
5 앱 (빈칸)
6 웹 30

여섯 명 가운데 셋째와 다섯째의 나이가 비어 있습니다. 이 두 칸이 결측치입니다. 가입 경로 열은 뒤에서 빈칸이 생긴 까닭을 가릴 때 씁니다.

「비어 있다」는 「0이다」와 다릅니다. 나이가 0인 회원은 없습니다. 빈칸은 값이 무엇인지 모른다는 뜻입니다. 이 차이를 놓치면 아래 소절들의 문제가 전부 생깁니다.

빈칸이 데이터에 적히는 모양

빈칸은 저장하는 곳마다 다른 표시로 남습니다. 표시는 달라도 뜻은 하나입니다. 이 값을 모른다는 뜻입니다. 백엔드에서 자주 만나는 표시는 넷입니다.

표시 주로 쓰는 곳 조심할 점
NULL 관계형 데이터베이스 비교하면 참도 거짓도 아닌 「모름」이 나온다
NaN(Not a Number, 숫자 아님) 부동소수점 계산 한 번 섞이면 계산 결과 전체가 NaN 이 된다
빈 문자열 CSV(Comma-Separated Values) 같은 텍스트 파일 읽는 프로그램에 따라 빈 글자로도, 빠진 값으로도 읽힌다
-1 · 9999 같은 표시 값 숫자만 받는 열 숫자처럼 생겨서 계산에 섞인다

표의 앞 두 줄은 빈칸이라는 뜻을 데이터에 담고 있습니다. 그래서 찾기도 쉽습니다. 뒤의 두 줄은 빈칸을 평범한 값처럼 적은 것이라 따로 찾아야 합니다.

가장 찾기 어려운 것은 마지막 줄입니다. 숫자만 받는 열에는 「모름」을 적을 방법이 없습니다. 그래서 -1 이나 9999 처럼 올 리 없는 숫자를 대신 넣습니다. 이런 값이 표시 값입니다. 데이터베이스는 이 값을 평범한 숫자로 보고 평균에 넣습니다.

빠진 값이 계산을 틀어 놓는 모습

앞의 여섯 명으로 회원 수와 평균 나이를 내 봅니다. 나이 열의 빈칸은 NULL 로 저장돼 있다고 합시다. 질의는 SQL(Structured Query Language)로 적습니다.

SQL
SELECT COUNT(*)   FROM member;  -- 6
SELECT COUNT(age) FROM member;  -- 4
SELECT AVG(age)   FROM member;  -- 30

COUNT(*) 는 행을 셉니다. COUNT(age) 는 나이가 있는 행만 셉니다. 둘의 차이 2가 빈칸의 수입니다.

AVG 는 여러 행을 값 하나로 줄이는 집계 함수입니다. AVG 는 나이가 NULL 인 행을 건너뜁니다. COUNT(age) 가 넷만 센 것과 같은 규칙입니다. 그래서 30은 여섯 명이 아니라 나이를 적은 넷의 평균입니다.

이번에는 빈칸을 0으로 치고 평균을 냅니다.

SQL
SELECT AVG(COALESCE(age, 0))
  FROM member;                  -- 20

COALESCE(age, 0) 은 나이가 비었으면 0을 대신 씁니다. 여섯 명의 합 120을 6으로 나눠 20이 나옵니다. 나이 0인 회원 둘이 새로 생긴 셈입니다.

빈칸이 처음부터 -1 로 적혀 있었다면 데이터베이스는 -1 을 나이로 믿습니다. 평균은 118을 6으로 나눈 약 19.7 이 됩니다. 세 질의 모두 오류를 내지 않습니다. 숫자만 다르게 돌려줍니다.

부동소수점 계산의 NaN 은 AVG 와 반대로 움직입니다. 빈칸을 건너뛰지 않고 결과로 번집니다. NaN 이 하나라도 섞인 합은 NaN 이 되고, 그 합으로 낸 평균도 NaN 입니다.

비교에서 빠지는 행

빈칸은 조건을 거를 때도 소리 없이 빠집니다. 나이가 30이 아닌 회원을 세어 봅니다.

SQL
SELECT COUNT(*) FROM member
 WHERE age <> 30;               -- 2

여섯 가운데 30인 둘을 빼면 넷이 나올 것 같습니다. 결과는 둘입니다. 20인 회원과 40인 회원만 들어왔습니다.

나이를 모르는 두 명은 30인지 아닌지 알 수 없습니다. 그래서 조건의 결과가 참도 거짓도 아닌 「모름」이 됩니다. 조건이 참인 행만 결과에 들어가므로 이 둘이 빠집니다. 참 · 거짓 · 모름 셋으로 따지는 이 규칙이 3값 논리입니다.

빈칸을 찾으려면 age IS NULL 로 따로 묻습니다. age = NULL 로 물으면 비교 결과가 늘 「모름」이라 한 행도 안 나옵니다.

NULL 을 = NULL 로 못 찾듯이 NaN 도 같다는 비교로는 못 찾습니다. NaN 은 자기 자신과 비교해도 같지 않다고 나오기 때문입니다. 그래서 NaN 인지 묻는 함수를 따로 씁니다.

빈칸이 생긴 까닭 세 갈래

다루는 방법을 고르기 전에 빈칸이 왜 생겼는지부터 봅니다. 까닭에 따라 빈칸 행을 지워도 되는지가 갈립니다. 통계학은 이 까닭을 셋으로 나눕니다.

갈래 빈칸이 생긴 까닭 빈칸 행을 지우면
완전 무작위 결측 어떤 값과도 상관없이 빠졌다 건수만 줄고 한쪽으로 안 쏠린다
무작위 결측 다른 열의 값을 보면 설명된다 그 열을 따라 쏠릴 수 있다
비무작위 결측 빠진 값 자신 때문에 빠졌다 쏠린다. 얼마나 쏠렸는지 데이터만으로는 모른다

완전 무작위 결측(MCAR, Missing Completely At Random)은 빈칸이 어떤 값과도 상관없이 생긴 경우입니다. 저장하다 오류가 나서 아무 회원이나 몇 명의 나이가 날아간 경우가 그렇습니다. 남은 회원은 전체에서 무작위로 뽑은 표본과 같습니다. 그래서 빈칸 행을 지워도 건수만 줄어듭니다.

무작위 결측은 빈칸이 다른 열로 설명되는 경우입니다. 줄여서 MAR(Missing At Random)라고 씁니다. 앞의 표에서 나이가 빈 두 명은 둘 다 앱으로 가입했습니다.

앱 가입 화면에서는 나이가 선택 입력이라고 해 봅시다. 빈칸은 나이가 아니라 가입 경로를 따라 생긴 것입니다.

이름에 「무작위」가 들었지만 이 빈칸은 아무렇게나 빠진 것이 아닙니다. 여기서 「무작위」는 가입 경로가 같은 회원끼리만 놓고 보면 무작위라는 뜻입니다. 앱 가입자가 대체로 어리다면 빈칸 행을 지운 평균은 웹 가입자 쪽으로 쏠립니다.

비무작위 결측(MNAR, Missing Not At Random)은 빈칸이 빠진 값 자신 때문에 생긴 경우입니다. 나이가 많은 회원일수록 나이 적기를 꺼린다고 해 봅시다. 그러면 빈칸 뒤에는 큰 값이 숨어 있습니다. 남은 값만으로 평균을 내면 실제보다 어리게 나옵니다.

셋 가운데 어느 것인지는 데이터가 스스로 말해 주지 않습니다. 빈칸이 생긴 경로를 입력 화면과 저장 코드에서 찾아봐야 합니다. 백엔드 개발자가 이 판단에 가장 가까이 있는 까닭입니다.

빈칸을 다루는 세 방법

까닭을 짐작했으면 방법을 고릅니다. 방법은 셋입니다. 빈칸 행을 지우기, 빈칸을 채우기, 빈 채로 두고 표시하기입니다. 아래 그림은 앞의 세 갈래를 따라 방법을 고르는 순서입니다.

flowchart TD
    A["빈칸을 찾았다"] --> B{"어떤 값과도 상관없이 빠졌나"}
    B -->|그렇다| C["빈칸 행을 지워도 된다"]
    B -->|아니다| D{"다른 열로 설명되나"}
    D -->|그렇다| E["같은 가입 경로 회원 값으로 채운다"]
    D -->|아니다| F["빈 채로 두고 비었다는 표시 열을 둔다"]

지우기는 가장 단순합니다. 빈칸이 하나라도 있는 행을 빼고 계산합니다. 앞의 AVG 가 이미 이렇게 했습니다. 열이 많으면 행마다 빈칸이 한 곳씩만 있어도 남는 행이 확 줄어듭니다.

빈칸에 그럴듯한 값을 넣는 일이 대체입니다. 흔한 방법은 나머지 값의 평균이나 중앙값을 넣는 것입니다. 앞의 여섯 명에 평균 30을 넣으면 나이는 30 · 20 · 30 · 40 · 30 · 30 이 됩니다.

평균은 30으로 안 변합니다. 대신 값이 가운데로 몰립니다. 원래 넷은 20에서 40 사이에 퍼져 있었습니다. 이제는 여섯 가운데 넷이 30입니다.

그래서 퍼짐을 재는 표준편차가 줄어듭니다. 넷만으로 재면 약 7.1 입니다. 평균으로 채운 여섯으로 재면 약 5.8 입니다. 데이터가 실제보다 고르게 보이는 것입니다.

무작위 결측이면 빈칸을 가입 경로가 같은 회원들의 값으로 채웁니다. 앱 가입자의 빈칸에는 앱 가입자들의 평균을 넣습니다. 가입 경로를 따라 생긴 쏠림을 그만큼 덜어 냅니다.

셋째 방법은 빈칸을 채우지 않고 빈 채로 두면서 비었다는 표시 열을 하나 더 두는 것입니다. 「나이를 적었나」를 참 · 거짓으로 담는 열입니다. 비무작위 결측처럼 빈칸 자체가 정보일 때 이 방법이 그 정보를 지킵니다.

어느 방법을 골랐는지는 결과 옆에 적습니다. 같은 여섯 명으로 20도 30도 나올 수 있기 때문입니다.

시계열에서 비는 구간

시간 순서로 쌓이는 값에서는 빈칸이 구간으로 생깁니다. 지표를 모으는 수집기가 멈추거나 네트워크가 끊기면 그동안의 점이 빠집니다. 1분마다 요청 수를 적는 시계열로 보면 이렇습니다.

시각 요청 수
10:00 120
10:01 (빈칸)
10:02 (빈칸)
10:03 150

이 두 칸을 어떻게 채우느냐에 따라 그래프가 다른 이야기를 합니다. 채우는 법은 흔히 셋입니다.

0으로 채우면 장애처럼 보입니다. 요청이 없었던 것이 아니라 세지 못한 것입니다. 그래프는 이 둘을 가르지 못합니다.

앞 값 채우기는 마지막으로 본 값을 빈칸에 이어 씁니다. 한동안 잘 안 바뀌는 지표에 맞습니다. 두 칸 모두 120이 됩니다.

보간은 앞뒤 값 사이를 이어 빈칸을 짐작합니다. 직선으로 이으면 120과 150 사이를 셋으로 나눠 130과 140이 들어갑니다.

채우는 법 10:01 10:02 그래프가 하는 말
0으로 0 0 요청이 뚝 끊겼다가 돌아왔다
앞 값으로 120 120 두 분 동안 멈췄다가 올랐다
직선 보간으로 130 140 고르게 올랐다

세 줄 가운데 무엇이 맞는지는 아무도 모릅니다. 셋 다 짐작입니다. 그래서 수집이 끊겼다는 것 자체를 따로 지켜보기도 합니다. 값이 한동안 안 들어오면 그것만으로 경보를 울리는 식입니다.

저장할 때 빈칸을 막는 법

결측치는 분석할 때 드러나지만 생기는 것은 저장할 때입니다. 그래서 백엔드에서 먼저 손쓸 수 있습니다.

꼭 있어야 할 열에는 스키마에 NOT NULL 제약을 겁니다. 그러면 빈칸이 든 행은 저장 단계에서 거절됩니다. 빈칸이 데이터에 들어오기 전에 입력한 쪽이 알게 됩니다.

반대로 모를 수 있는 값에 빈칸을 막으면 문제가 옮겨 갈 뿐입니다. 입력하는 쪽이 -1 같은 표시 값을 지어 넣게 됩니다. 모를 수 있는 열은 빈칸을 허용하고 NULL 로 남겨 둡니다. 그래야 뒤에서 IS NULL 한 줄로 찾을 수 있습니다.

이상치와 가르는 법

이상치는 값이 있습니다. 그 값이 무리에서 멀리 떨어져 있을 뿐입니다. 결측치는 값 자체가 없습니다. 둘은 서로 다른 문제입니다.

헷갈리는 것은 표시 값입니다. 나이 열에 섞인 -1 이나 9999 는 히스토그램에서 무리와 동떨어진 막대로 섭니다. 그래서 이상치처럼 보입니다. 그 값은 멀리 떨어진 나이가 아니라 모른다는 표시입니다.

데이터를 처음 받아 값마다 건수를 세어 그래프로 그려 보는 일이 탐색적 자료 분석입니다. 이 단계에서 표시 값이 드러납니다. 이상치로 보고 지우기 전에 표시 값인지부터 확인합니다. 확인한 표시 값은 데이터 정제에서 빈칸으로 바꿔 둡니다.

관련 항목

결측치가 데이터에 적히는 표시

NULL · NaN · 표시 값 · 빈 문자열 · CSV · 부동소수점

결측치를 다루는 SQL 문법

SQL · 3값 논리 · 집계 함수 · COALESCE · IS NULL · NOT NULL 제약

빈칸이 생긴 까닭을 가르는 분류

완전 무작위 결측 · 무작위 결측 · 비무작위 결측 · 선택 편향 · 표본

결측치를 지우거나 채우는 처리 방법

목록별 삭제 · 결측치 대체 · 평균 대체 · 다중 대체 · 앞 값 채우기 · 보간

결측치에 흔들리는 요약값

평균 · 중앙값 · 표준편차 · 분산 · 집계

결측치를 찾아내는 분석 작업

탐색적 자료 분석 · 데이터 정제 · 데이터 품질 · 데이터 분석 · 이상치 · 히스토그램

빈 구간이 생기는 지표 수집

시계열 · 수집기 · 메트릭 · 샘플링 · 경보

저장할 때 빈칸을 막는 제약

스키마 · 관계형 데이터베이스 · 입력 검증 · 기본값 · 무결성 제약

다른 이름: missing value · 결측값 · 누락값