복습 음성 · 약 16분

전처리에서는 결측치·중복·이상치를 확인하고 스케일링, 인코딩, 구간화 등을 적용한다. 함수 이름과 함께 각 처리가 필요한 이유와 적용 순서를 기록해둔다.

데이터에서 구하는 기준은 학습 데이터에서 정하고, 새 데이터에도 같은 기준을 적용한다. 결측치를 채울 때도 스케일링할 때도 이 순서는 같다.

1. 먼저 데이터가 어떻게 생겼는지 확인

Data preprocessing(데이터 전처리)은 수집한 데이터를 살펴보고 분석이나 모델에 쓸 수 있도록 정리하는 과정이다. 빈칸을 채우는 것 외에도 자료형, 단위, 중복, 숫자의 범위 등을 확인한다.

예를 들어 주문 데이터라면 한 행이 주문 한 건인지, 하루치 합계인지부터 알아야 한다. 금액 1000도 단위가 원인지 천 원인지에 따라 뜻이 달라진다. 숫자로 보여도 문자열로 저장되어 있으면 계산 전에 자료형을 바꿔야 한다.

처음 데이터를 열었을 때 확인할 것들을 적어두면 다음과 같다.

  • shape: 행과 열 개수
  • head(): 앞부분의 실제 값
  • info(), dtypes: 자료형과 비어 있지 않은 값의 수
  • describe(): 평균, 사분위수, 최솟값·최댓값 같은 요약
  • isna().sum(), isna().mean(): 열별 결측 개수와 비율

원본은 남겨두고 복사본에서 작업한다. 나중에 행이 줄거나 값이 바뀌었을 때 무엇을 했는지 비교하기 위해서다.

Target(정답)과 Feature(특성)도 구분한다. 예측할 시점에 알 수 없는 값을 특성으로 넣으면 실제로 사용할 때 같은 결과를 기대하기 어렵다. 예를 들어 다음 달 매출을 예측하면서 다음 달에야 확정되는 정보를 넣는 경우다.

2. 기준을 계산하기 전에 학습용·테스트용을 나눈다

결측치를 채울 평균이나 중앙값, 스케일링에 쓸 최솟값·최댓값은 데이터에서 구하는 값이다. 이런 계산에 테스트 데이터까지 넣으면 평가에 쓸 정보가 학습 과정에 섞인다. 이를 Data leakage(데이터 누수)라고 한다.

그래서 Train set(학습 세트)에서 기준을 구하고, Validation set(검증 세트)과 Test set(테스트 세트)에는 그 기준을 그대로 적용한다.

원본 보존, 열의 뜻·단위·수집 오류 확인
→ 학습 / 검증 / 테스트 분리
→ 학습 데이터에서 대체값·스케일·범주·구간 기준 구하기
→ 검증·테스트에 같은 기준 적용
→ 처리 결과 확인

fit()은 기준을 구하는 단계, transform()은 그 기준으로 값을 바꾸는 단계다. fit_transform()은 두 단계를 이어서 한다. 테스트에 다시 fit_transform()을 쓰면 테스트 기준을 새로 계산하게 된다. 데이터 누수 참고

분리 방법도 데이터에 맞춰야 한다. 미래를 예측하는 문제는 시간순으로 나누는 방법을 검토한다. 같은 고객의 여러 기록이 있다면 고객 단위로 나눠야 하는지도 살펴본다. 같은 사람이나 같은 사건이 양쪽에 섞이면 새 데이터에 대한 성능을 제대로 보기 어려울 수 있다.

3. 결측치: 왜 비었는지부터 보기

Missing value(결측치)는 값이 비어 있는 상태다. pandas에서는 자료형에 따라 NaN, None, pd.NA, NaT 등으로 나타날 수 있다. 확인할 때는 isna()를 사용한다.

결측 개수와 비율은 같이 봐야 한다. 빈칸 10개라도 전체가 20행인지 10만 행인지에 따라 상황이 다르다. 그리고 매출 0원과 매출을 수집하지 못한 빈칸도 뜻이 다르다.

삭제할지, 채울지

정보가 거의 없는 행이나 필요 없는 열은 삭제할 수 있다. 다만 빈칸이 있는 행을 전부 지우면 특정 집단의 자료가 많이 사라질 수 있다.

dropna()의 옵션은 구분해서 기억해둔다. 기본 행 삭제 기준에서 how="any"는 검사하는 열 중 하나라도 비면 제거하고, how="all"은 검사하는 열이 모두 비었을 때 제거한다. subset으로 검사할 열을 정할 수 있고, axis=1은 열을 제거하는 방향이다. dropna 옵션

채울 때도 선택지가 있다.

  • 평균: 수치형 데이터의 대표값으로 쓸 수 있지만 큰 값에 영향을 받는다.
  • 중앙값: 값을 정렬했을 때 가운데 값. 일부 극단값에 평균보다 덜 흔들린다.
  • 최빈값: 가장 자주 나온 값. 범주형 데이터의 빈칸을 채울 때도 쓴다.
  • 정해둔 값: ‘미상’ 같은 별도 범주를 쓰는 방법. 0을 넣을 때는 실제 0과 의미가 겹치는지 확인한다.

관측값이 10, 20, 30이라면 중앙값은 20이다. 학습 데이터에서 구한 이 값으로 학습과 테스트의 빈칸을 모두 채운다. 빈칸이었다는 사실이 의미가 있다면, 원래 결측이었는지를 0과 1로 표시하는 열을 추가할 수도 있다.

이미 알고 있는 업무 공식으로 값을 복구할 수 있는지도 먼저 본다. 수량과 단가로 금액이 정확히 정해지는 자료라면 그 관계를 확인하는 식이다. 이런 경우에도 예측 시점에 사용할 수 있는 정보인지 확인해야 한다.

시간 데이터의 보간

Interpolation(보간)은 주변 값 사이를 이어 빈칸을 추정하는 방법이다. 같은 간격으로 기록된 10, 빈칸, 30을 선형 보간하면 가운데가 20이 된다.

시간순으로 정렬하고 같은 고객·같은 지역처럼 이어지는 자료 안에서 적용해야 한다. 서로 다른 대상의 값을 이어서 채우면 의미가 달라진다. 과거 보고서를 완성할 때와 미래 예측용 데이터를 만들 때도 구분한다. 예측 당시 알 수 없던 미래 값으로 과거 빈칸을 채우면 정보가 새어 들어간다.

4. 중복: 같은 값과 같은 사건 구분하기

Duplicate(중복)는 같은 기록이 반복해서 들어간 경우다. 같은 고객이 같은 상품을 두 번 샀다면 두 건 모두 필요한 기록일 수 있다. 수집 과정에서 한 주문이 두 번 저장됐다면 정리할 대상이다.

duplicated()로 중복 여부를 확인하고 drop_duplicates()로 정리할 수 있다. duplicated(keep=False)는 중복된 묶음 전체를 표시해서 살펴볼 때 유용하다.

어떤 열을 기준으로 같은 사건인지 판단할지도 정해야 한다. 주문 번호처럼 한 건을 구분하는 Business key(업무상 고유 키)를 사용할 수 있고, 고객과 시각 등 여러 열의 조합이 필요할 수도 있다.

수정 이력이 있는 자료에서 최신 기록을 남기려면 실제 수정 시각으로 정렬한 뒤 선택한다. keep="last"가 남기는 것은 현재 행 순서의 마지막 기록이다. 인덱스 숫자가 가장 크다고 최신 기록인 것은 아니다.

중복 수집으로 확인된 기록이 학습과 테스트에 각각 들어가지 않도록 분리 전에 정리한다. 정상적인 반복 기록은 그대로 두되, 고객 단위 분리가 필요한지 따로 판단한다.

5. 이상치: 발견했다고 바로 지우지는 않기

Outlier(이상치)는 다른 값에서 크게 떨어진 관측값이다. 35세를 350세로 입력한 오류일 수도 있고, 평소보다 큰 단체 주문처럼 실제 사건일 수도 있다. 먼저 원자료와 단위, 날짜, 대상의 특성을 확인한다.

Histogram(히스토그램)이나 Box plot(상자 그림)으로 분포를 보고, 수치 기준으로 확인할 후보를 고를 수도 있다. 수업에서 나온 방법은 Z-score와 IQR이다.

Z-score

z = (값 - 평균) / 표준편차

평균에서 표준편차 몇 개만큼 떨어졌는지를 나타낸다. 예를 들어 평균이 100, 표준편차가 20이면 160의 Z-score는 3이다.

|z| > 3 같은 기준을 쓰기도 하지만 분포에 상관없이 적용되는 규칙은 아니다. 한쪽으로 치우친 데이터나 서로 다른 집단을 섞은 데이터에서는 해석에 주의해야 한다. 평균과 표준편차 자체도 극단값의 영향을 받는다.

IQR

IQR(사분위 범위)은 가운데 50%가 차지하는 폭이다. Q1은 25% 지점, Q3는 75% 지점이다.

IQR = Q3 - Q1
아래 경계 = Q1 - 1.5 × IQR
위 경계 = Q3 + 1.5 × IQR

Q1이 10, Q3가 30이면 IQR은 20이고, 확인할 경계는 -20과 60이다. 60보다 큰 값은 이 기준에서 이상치 후보가 된다. 이 사실만으로 입력 오류라고 확정할 수는 없다.

원인에 따라 오류를 수정하거나, 값을 유지하거나, 별도로 분석한다. Clipping(범위 제한)이나 변환을 쓰는 경우도 있다. 삭제한다면 어떤 이유로 몇 행을 지웠는지 남긴다. 데이터에서 경계를 계산한다면 그 경계도 학습 데이터에서 정한다.

6. 스케일링: 열마다 다른 숫자 크기 맞추기

방문 횟수는 1~5인데 구매 금액은 1,000~100,000이라면 숫자 크기가 많이 다르다. Feature scaling(특성 스케일링)은 이런 차이를 조정한다. 거리 계산이나 경사 기반 학습, 규제에 민감한 모델에서 특히 살펴볼 부분이다. 트리 기반 모델은 일반적으로 스케일 변화에 덜 민감하다.

StandardScaler

Standardization(표준화)은 평균을 빼고 표준편차로 나눈다.

변환값 = (값 - 학습 평균) / 학습 표준편차

기본 설정에서 상수가 아닌 학습 열의 평균을 0, 분산을 1에 맞춘다. 분포를 정규분포로 만들어주는 기능은 없다. 값의 중심과 크기를 바꾼다. 평균·표준편차를 사용하므로 극단값의 영향도 받는다. StandardScaler

MinMaxScaler

기본 설정에서는 학습 데이터의 최솟값을 0, 최댓값을 1로 맞춘다.

변환값 = (값 - 학습 최솟값) / (학습 최댓값 - 학습 최솟값)

학습 범위가 10~30이면 10은 0, 20은 0.5, 30은 1이 된다. 새로 들어온 40은 (40 - 10) / (30 - 10) = 1.5가 된다.

테스트 값은 0~1을 벗어날 수 있다. 기본값인 clip=False에서 학습 범위 밖의 값이 들어오면 생기는 결과다. 큰 극단값 하나 때문에 나머지 값이 좁은 구간에 몰릴 수도 있다. MinMaxScaler

RobustScaler

기본 설정에서 중앙값과 IQR을 기준으로 크기를 조정한다. 평균·표준편차를 쓰는 방법보다 일부 극단값에 덜 흔들리도록 만든 방식이다. 변환 후에도 큰 값은 남아 있을 수 있으므로 이상치 처리를 대신한다고 생각하면 안 된다. RobustScaler

Normalization(정규화)이라는 말도 헷갈릴 수 있다. 문맥에 따라 Min-Max 변환을 가리키기도 하고, 벡터의 길이를 맞추는 변환을 가리키기도 한다. 모델의 가중치에 제약을 주는 Regularization(정칙화)과도 구분해서 기억해둔다.

어떤 스케일러를 쓸지는 모델과 데이터에 따라 고른다. 스케일링만으로 성능이 반드시 좋아지는 것은 아니므로 같은 분할에서 결과를 비교해야 한다.

7. 결측치 채우기 → 스케일링 직접 해보기

작은 숫자 데이터로 순서를 확인했다. 학습용은 10, 20, 빈칸, 30, 테스트용은 40, 빈칸이다. 미리 두 묶음으로 정한 예제이며, 각 행에 숫자형 특성이 하나씩 있다.

import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import MinMaxScaler

X_train = np.array([[10], [20], [np.nan], [30]], dtype=float)
X_test = np.array([[40], [np.nan]], dtype=float)

# 학습 데이터의 중앙값으로 빈칸 채우기
imputer = SimpleImputer(strategy="median")
train_filled = imputer.fit_transform(X_train)
test_filled = imputer.transform(X_test)

# 학습 데이터의 범위로 스케일링
scaler = MinMaxScaler()
train_ready = scaler.fit_transform(train_filled)
test_ready = scaler.transform(test_filled)

print("Train:", train_ready.ravel().tolist())
print("Test:", test_ready.ravel().tolist())

실행 결과:

Train: [0.0, 0.5, 0.5, 1.0]
Test: [1.5, 0.5]

먼저 학습 데이터의 중앙값 20으로 빈칸을 채운다. 학습 데이터는 [10, 20, 20, 30], 테스트 데이터는 [40, 20]이 된다.

그다음 학습 데이터에서 최솟값 10과 최댓값 30을 구해 스케일링한다. 테스트의 40 때문에 학습 최댓값이 바뀌지는 않는다. 그래서 40은 1.5, 빈칸을 채운 20은 0.5가 된다.

여기서는 값이 바뀌는 과정만 확인했다. 예측 모델의 학습이나 성능 평가까지 한 예제는 아니다.

8. 인코딩: 문자 범주를 모델 입력으로 바꾸기

‘매장’, ‘배달’, ‘포장’처럼 종류를 나타내는 값은 Categorical data(범주형 데이터)다. 이를 0, 1, 2로만 바꾸면 모델에 따라 크기나 간격이 있는 숫자로 해석할 수 있다.

순서가 없는 범주에는 One-hot encoding(원핫 인코딩)을 사용할 수 있다. 범주마다 열을 하나 만들고, 해당하는 열만 1로 표시한다.

       매장  배달  포장
매장     1    0    0
배달     0    1    0
포장     0    0    1

pd.get_dummies()로 간단히 만들어볼 수 있다. 다만 학습과 테스트에서 각각 실행하면 등장한 범주가 달라 열 구성이 어긋날 수 있다. 학습에는 ‘포장’이 있었는데 테스트에는 없는 경우를 생각하면 된다.

OneHotEncoder를 학습 데이터에 fit하고 테스트에는 transform하면 학습 때의 열 구성을 유지할 수 있다. handle_unknown="ignore"를 쓰면 처음 보는 범주는 해당 특성의 원핫 열 묶음에서 모두 0으로 표시한다. OneHotEncoder

원핫 인코딩에도 한계는 있다. 고유한 범주가 매우 많으면 열도 많아진다. ‘낮음·보통·높음’처럼 실제 순서가 있다면 그 순서를 명시한 Ordinal encoding(순서형 인코딩)을 검토할 수 있다. 범주형 값을 직접 처리하는 모델인지도 확인한다.

9. 구간화: 연속된 숫자를 몇 개의 구간으로 묶기

Binning(구간화)은 연속값을 범주로 나누는 작업이다. 나이를 연령대로 묶는 경우를 떠올리면 쉽다. 설명하기는 편해지지만, 같은 구간 안의 차이는 사라진다.

cut()과 qcut()의 기준이 다르다는 점을 기억해둔다.

  • pd.cut(): 값의 경계로 나눈다. [0, 20, 40, 60]처럼 경계를 지정하거나, 구간 수를 주어 같은 폭으로 나눌 수 있다. 각 구간의 데이터 개수는 달라도 된다.
  • pd.qcut(): Quantile(분위수)을 기준으로 데이터 개수가 비슷하도록 나눈다. 값이 몰린 곳에서는 구간 폭이 좁아질 수 있다. 같은 값이 많으면 원하는 수의 구간을 만들기 어려울 수도 있다.

예를 들어 값이 10, 11, 12, 90, 95, 100이라면 같은 폭으로 자르는 것과 같은 개수로 나누는 결과가 달라진다. 구간화의 목적이 무엇인지 먼저 정해야 한다. cut · qcut

분위수처럼 데이터에서 경계를 계산했다면 학습에서 정한 경계를 테스트에도 사용한다. 테스트에 별도로 qcut()을 실행하면 같은 숫자가 다른 구간으로 분류될 수 있다. 경계 밖으로 들어오는 새 값을 어떻게 처리할지도 정해둔다.

10. 순서를 묶어두고, 처리 결과 다시 확인

이번 실습은 과정을 보려고 결측치 대체와 스케일링을 따로 실행했다. 단계가 늘어나면 Pipeline(파이프라인)으로 순서를 묶을 수 있다. 숫자형 열에는 결측치 대체와 스케일링을, 문자형 열에는 결측치 대체와 인코딩을 적용하는 식이다. 열 종류별로 다른 처리를 연결할 때는 ColumnTransformer(열별 변환기)를 쓴다.

Cross-validation(교차 검증)을 할 때도 각 분할의 학습 부분에서만 전처리 기준을 구해야 한다. 전처리를 포함한 파이프라인 전체를 교차 검증에 넣으면 이 순서를 유지하는 데 도움이 된다. 전체 데이터로 미리 전처리한 뒤 교차 검증하면 검증 부분의 정보가 이미 섞일 수 있다.

끝났을 때는 아래 정도를 다시 살펴본다.

  • 행이 몇 개 줄었고, 어떤 이유로 줄었는가?
  • 결측치와 중복은 얼마나 남았는가?
  • 열 이름, 순서, 자료형은 예상한 대로인가?
  • 값의 분포와 업무상 계산 관계가 크게 어긋나지 않았는가?
  • 처음 보는 범주나 학습 범위 밖의 값도 처리할 수 있는가?
  • 모델까지 학습했다면 같은 분할에서 성능과 오류 사례가 어떻게 달라졌는가?

전처리 방법마다 왜 필요한지 설명할 수 있어야겠다. 빈칸을 왜 그 값으로 채웠는지, 어떤 행을 왜 지웠는지, 기준을 어느 데이터에서 구했는지도 함께 남겨둔다.


AIFFEL 수업을 공부하며 만든 개인 노트를 바탕으로 다시 정리했다. 글의 숫자 예제는 설명용으로 따로 만들었고, 위 음성은 기존 복습용 TTS(음성 합성) 파일이다.