Databricks ML Associate

Databricks ML Associate 시험 노트개념 정리15 MIN

평균·중앙값·최빈값으로 결측값 대치

평균·중앙값·최빈값 대치가 치우친 분포에서 서로 다른 값을 채우는 이유를 숫자로 보고, Spark ML Imputer와 결측 표시 열, 분할 뒤에 fit해야 하는 까닭까지 정리합니다.

결측값(missing value)은 있어야 할 자리에 값이 비어 있는 칸입니다. Spark DataFrame에서는 대개 null로, 부동소수점 열에서는 NaN으로 나타납니다. 대부분의 Spark ML 알고리즘은 빈 칸을 받지 못하므로 학습 전에 무엇이든 해야 하고, 선택지는 행을 버리거나 값을 채우는 것입니다. 빈 칸에 그럴듯한 값을 채워 넣는 일을 대치(imputation)라고 부릅니다.

앞 노트에서 이상치를 가를 때 평균과 사분위수가 서로 다른 답을 냈습니다. 대치에서도 같은 일이 일어납니다. 무엇으로 채우느냐가 곧 「이 열의 대표값을 무엇으로 볼 것인가」이고, 시험은 분포와 열의 종류를 주고 알맞은 대표값을 고르게 합니다.

세 가지 대표값

평균과 중앙값

평균은 모든 값을 더해 개수로 나눈 것이고, 중앙값은 값을 크기 순으로 줄 세웠을 때 가운데 오는 값입니다. 개수가 짝수면 가운데 둘의 평균을 씁니다. 두 값은 분포가 좌우 대칭이면 거의 같고, 한쪽 꼬리가 길면 벌어집니다.

월 사용액 열에 값 여덟 개와 빈 칸 둘이 있다고 합시다(단위: 만 원).

20  22  24  25  26  28  30  145  null  null

관측된 여덟 값의 합은 320이므로 평균은 320/8=40320 / 8 = 40 입니다. 중앙값은 넷째·다섯째 값인 25와 26의 평균, 곧 25.5입니다. 평균 대치를 하면 빈 칸 둘에 40이 들어가는데, 이 값은 관측된 여덟 값 중 일곱보다 큽니다. 145 하나가 평균을 끌어올린 탓입니다.

최빈값

최빈값은 가장 여러 번 나온 값입니다. 결제 수단 열에 카드 다섯, 계좌이체 둘, 간편결제 하나가 있고 빈 칸이 둘이면 최빈값 대치는 빈 칸 둘을 모두 「카드」로 채웁니다. 평균이나 중앙값은 순서나 크기가 있어야 계산되지만 최빈값은 세기만 하면 되므로 범주형 변수, 곧 값이 몇 개의 이름 중 하나로 정해지는 열에도 쓸 수 있습니다.

대치 방법 고르기

치우침과 이상치

고르는 첫 기준은 분포 모양입니다. 오른쪽 꼬리가 긴 분포에서는 평균이 중앙값보다 크고, 이상치가 끼어 있으면 그 차이가 더 벌어집니다. 위 예처럼 평균으로 채우면 대부분의 고객보다 훨씬 많이 쓰는 가상의 고객이 둘 생기는 셈입니다. 중앙값은 순서만 보므로 145가 1,450이 되어도 25.5에서 움직이지 않습니다. 그래서 치우친 분포나 이상치가 있는 열에는 중앙값 대치가 기본이고, 대칭에 가까운 열에서는 평균과 중앙값이 비슷해 어느 쪽을 써도 무방합니다.

연속형과 범주형

두 번째 기준은 열의 종류입니다.

열의 종류 쓸 수 있는 대치 쓰지 않는 대치
연속형, 대칭 평균, 중앙값 —
연속형, 치우침·이상치 중앙값 평균
범주형 최빈값, 「unknown」 같은 별도 범주 평균, 중앙값

범주형에 평균을 쓸 수 없는 것은 당연해 보이지만, 범주를 숫자 코드로 바꿔 둔 뒤라면 계산 자체는 돌아간다는 점이 함정입니다. 지역 코드 1·2·3의 평균 2.0은 계산은 되지만 뜻이 없는 값입니다. 결측이 많은 범주형 열이라면 최빈값으로 덮기보다 「unknown」이라는 범주를 하나 새로 만드는 편이 정보를 덜 잃습니다.

분산 축소

어느 대표값이든 빈 칸 전부를 같은 값 하나로 채운다는 점은 같습니다. 그래서 대치 뒤에는 값이 가운데로 몰리고 분산이 실제보다 작아집니다. 값 2·4·6·8에 빈 칸 하나가 있을 때 평균 5로 채우면, 모분산이 (9+1+1+9)/4=5(9+1+1+9)/4 = 5 에서 (9+1+0+1+9)/5=4(9+1+0+1+9)/5 = 4 로 줄어듭니다. 결측 비율이 높을수록 이 왜곡이 커지므로, 결측이 절반을 넘는 열이면 채울지부터 다시 묻는 것이 맞습니다.

Spark ML Imputer

기본 사용법

Spark ML에서는 pyspark.ml.feature.Imputer가 이 일을 합니다. strategy로 "mean"·"median"·"mode" 가운데 하나를 고르고, 채울 열과 결과를 쓸 열을 짝지어 줍니다.

from pyspark.ml.feature import Imputer

imputer = Imputer(
    strategy="median",
    inputCols=["monthly_spend", "tenure_months"],
    outputCols=["monthly_spend_imp", "tenure_months_imp"],
)
imputer_model = imputer.fit(train_df)          # 학습 세트에서 중앙값을 계산
train_imp = imputer_model.transform(train_df)
test_imp = imputer_model.transform(test_df)    # 같은 값으로 채운다

Imputer는 추정기(Estimator)입니다 — fit이 데이터에서 대표값을 계산해 변환기(Transformer)인 ImputerModel을 돌려주고, 실제로 채우는 것은 그 모델의 transform입니다. 계산된 값은 imputer_model.surrogateDF로 확인할 수 있습니다.

인자와 제약

  • strategy의 기본값은 "mean"입니다. 치우친 열에 아무 생각 없이 쓰면 평균이 들어갑니다.
  • missingValue는 무엇을 결측으로 볼지 정하며 기본값은 NaN입니다. null은 이 값과 상관없이 늘 결측으로 취급됩니다. -1이나 999처럼 결측을 숫자로 표시해 둔 데이터라면 여기에 그 값을 줍니다.
  • 중앙값은 정확한 값이 아니라 근사로 구하며 relativeError가 그 허용 오차입니다.
  • 대표값은 결측을 뺀 나머지 값에서 계산합니다.
  • 입력은 숫자 열이어야 합니다. 문자열 범주형 열은 Imputer에 넣지 않고 fillna로 채웁니다.
train_df = train_df.fillna({"pay_method": "unknown"})

결측 표시 열

결측에 담긴 정보

값이 비어 있다는 사실 자체가 정보일 때가 있습니다. 소득을 적지 않은 신청자가 대출을 더 자주 연체한다면, 중앙값으로 채우는 순간 그 신호가 지워집니다. 채운 뒤에는 원래 값이 있던 행과 채운 행을 모델이 구별할 수 없기 때문입니다.

결측 표시 열(missing indicator)은 원래 그 칸이 비어 있었는지를 1과 0으로 적어 두는 새 열입니다. 대치된 열과 표시 열을 함께 넣으면, 모델은 채운 값으로 크기를 쓰고 표시 열로 「비어 있었음」을 따로 배웁니다.

만드는 순서

표시 열은 반드시 대치하기 전에 만듭니다. 채운 뒤에는 어느 칸이 비어 있었는지 알 수 없습니다. Spark의 Imputer에는 표시 열을 만드는 옵션이 없으므로 직접 씁니다.

from pyspark.sql import functions as F

df = df.withColumn("income_missing", F.col("income").isNull().cast("int"))  # 그다음에 대치

데이터 누수

분할 전 대치

데이터 누수(data leakage)는 학습할 때 알아서는 안 되는 정보, 특히 검증·테스트 세트의 정보가 모델 쪽으로 새어 들어가는 일입니다. 대치에서 흔한 형태는 전체 데이터로 평균이나 중앙값을 먼저 구해 채운 뒤에 학습·테스트로 나누는 것입니다. 그러면 테스트 행들의 값이 대표값 계산에 섞여 들어가고, 테스트 점수는 모델이 처음 보는 데이터에서 낼 점수보다 좋게 나옵니다.

학습 세트의 값이 10, 20, 30이고 테스트 세트의 값이 100이라고 합시다. 학습 세트만의 평균은 20이지만 넷을 합쳐 구하면 160/4=40160/4 = 40 입니다. 분할 전에 대치하면 학습 세트의 빈 칸에 40이 들어가는데, 이 40에는 테스트 세트의 100이 녹아 있습니다.

fit과 transform의 분리

막는 방법은 순서를 지키는 것입니다. 먼저 나누고, 학습 세트로만 fit하고, 그 모델로 학습·검증·테스트를 모두 transform합니다. 추론 때도 새 데이터로 다시 fit하지 않고 저장해 둔 모델을 씁니다. 이 순서를 가장 확실히 지키는 방법은 Imputer를 Pipeline의 한 단계로 넣어 파이프라인 전체를 학습 세트로 fit하는 것입니다. 교차 검증을 돌려도 폴드마다 학습 부분으로만 대표값을 다시 계산하게 됩니다.

시험은 코드 조각을 보여 주고 「어디가 잘못인가」를 자주 묻습니다. imputer.fit(df) 다음 줄에 randomSplit이 오면 그 순서 자체가 답입니다.

연습 문제

  1. 값 10, 12, 14, 16, 88과 빈 칸 하나가 있습니다. 평균 대치로 빈 칸에 들어가는 값은?
    ① 14
    ② 16
    ③ 28
    ④ 35
    ③. 합이 10+12+14+16+88=14010+12+14+16+88 = 140 이므로 평균은 140/5=28140/5 = 28 입니다. 중앙값 대치였다면 14가 들어갑니다.
  2. 1번 데이터에 가장 알맞은 대치 방법과 그 이유는?
    ① 평균. 모든 값을 반영한다
    ② 중앙값. 88 하나가 평균을 끌어올린다
    ③ 최빈값. 연속형에는 최빈값이 기본이다
    ④ 0으로 채운다. 값이 없다는 뜻이다
    ②. 평균 28은 관측값 다섯 중 넷보다 큽니다. 연속형에서 최빈값은 값이 거의 겹치지 않아 대표성이 약하고, 0은 「없음」이 아니라 새로운 값을 지어내는 것입니다.
  3. 값 2, 4, 6, 8과 빈 칸 하나가 있는 열을 평균으로 대치했습니다. 대치 뒤 다섯 값의 모분산은?
    ① 3
    ② 4
    ③ 5
    ④ 6.25
    ②. 평균은 5이고 대치 뒤 값은 2·4·5·6·8입니다. 편차 제곱의 합이 9+1+0+1+9=209+1+0+1+9 = 20 이므로 20/5=420/5 = 4 입니다. 대치 전 모분산 5보다 작아졌습니다.
  4. Imputer에 대한 설명으로 옳은 것을 둘 고르시오.
    ① strategy의 기본값은 "median"이다
    ② strategy에 "mode"를 줄 수 있다
    ③ null은 missingValue 설정과 상관없이 결측으로 취급된다
    ④ 문자열 범주형 열을 그대로 넣으면 최빈값으로 채운다
    ⑤ fit 없이 transform만으로 동작한다
    ②와 ③. 기본값은 "mean"이고, 입력은 숫자 열이어야 하며, 추정기라 fit으로 ImputerModel을 먼저 만들어야 합니다.
  5. 다음 코드의 문제로 가장 알맞은 것은?
    model = Imputer(strategy="median", inputCols=["x"], outputCols=["x_imp"]).fit(df)
    df2 = model.transform(df)
    train, test = df2.randomSplit([0.8, 0.2], seed=42)
    ① strategy에 "median"을 줄 수 없다
    ② 전체 데이터로 중앙값을 계산해 테스트 정보가 학습에 새어 든다
    ③ randomSplit의 비율 합이 1이 아니다
    ④ outputCols를 쓰면 원래 열이 지워진다
    ②. 먼저 나누고 train으로만 fit해야 합니다.
  6. 학습 세트 값이 10, 20, 30, 테스트 세트 값이 100입니다. 분할 전에 전체 평균으로 대치하면 학습 세트 빈 칸에 들어가는 값과, 올바르게 대치했을 때의 값의 차이는?
    ① 0
    ② 10
    ③ 20
    ④ 40
    ③. 전체 평균은 160/4=40160/4 = 40 이고 학습 세트 평균은 60/3=2060/3 = 20 이라 차이가 20입니다.
  7. 소득을 적지 않은 신청자의 연체율이 눈에 띄게 높습니다. 소득 열을 다루는 방법으로 가장 알맞은 것은?
    ① 결측 행을 모두 제거한다
    ② 평균으로 대치한다
    ③ 결측 표시 열을 먼저 만든 뒤 중앙값으로 대치한다
    ④ 중앙값으로 대치한 뒤 결측 표시 열을 만든다
    ③. 결측 자체가 신호이므로 표시 열로 남겨야 하고, 대치한 뒤에는 어느 칸이 비어 있었는지 알 수 없어 ④는 순서가 틀렸습니다.

정리하면 대표값은 분포와 열의 종류가 고르고, 대표값을 계산하는 데이터는 학습 세트뿐입니다. 치우쳤으면 중앙값, 범주형이면 최빈값이나 별도 범주, 비어 있다는 사실이 중요하면 표시 열을 먼저 남깁니다.

Databricks ML Associate 시험 노트 전체 보기