데이터 정제 파이프라인: 중복 제거와 스케일·분포 변환 운영법
중복 제거, 정규화·표준화, 로그·Box-Cox 변환을 데이터 품질과 머신러닝 운영 관점에서 정리한다.
2026-08-14 · 최초 발행 2025-10-14
데이터 품질을 모델 입력까지 연결하는 정제 작업
분석과 머신러닝에서 데이터 정제는 오류, 중복, 왜도, 피처 간 규모 차이를 처리하는 선행 작업이다. 중복 제거로 엔터티를 하나로 맞추고, 정규화·표준화로 입력 스케일을 정렬하며, 로그 또는 Box-Cox 변환으로 긴 꼬리 분포를 완화한다.
이 작업은 단발성 전처리보다 재현 가능한 파이프라인으로 다뤄야 한다. 어떤 규칙과 파라미터를 적용했는지 남기지 않으면 학습 데이터와 서빙 데이터가 다른 변환을 거칠 수 있다.
중복·스케일·왜도를 다루는 방식
중복 제거는 하나의 엔터티가 여러 레코드로 저장된 상태를 찾아 병합하는 과정이다. 정규화된 이메일이나 전화번호처럼 결정적 키를 사용하면 정확한 매칭과 재현성에 유리하다. 이름처럼 완전히 일치하지 않는 값은 Jaro-Winkler, Levenshtein, TF-IDF 코사인 유사도 등의 퍼지 매칭으로 근접 항목을 찾을 수 있지만 계산 비용이 증가한다.
정규화는 값을 [0,1] 같은 특정 범위로 옮기는 방식이며 Min-Max 스케일링이 대표적이다. 표준화는 평균을 0, 표준편차를 1로 바꾸는 z-score 변환으로, 모델 최적화의 수렴 안정화에 기여한다. 어떤 방식을 택하든 피처별 스케일을 일관되게 유지하고 학습과 서빙에 같은 파라미터를 적용해야 한다.
로그 변환은 log(x) 또는 log1p(x)로 왜도가 큰 분포의 꼬리를 줄인다. 입력은 0 이상이어야 한다. Box-Cox 변환은 λ 파라미터로 변환 강도를 조절하며 양수 입력이 필요하므로, 필요할 때는 시프트로 값을 양수화한다.
프로파일링부터 검증까지 이어지는 흐름
정제 규칙을 정하기 전에 결측률, 중복률, 왜도·첨도, 이상치 비율을 측정한다. 이 지표를 기준으로 중복 제거 방식과 스케일러, 분포 변환을 선택한다. 감시 지표는 버전과 함께 남겨 파이프라인 변경이 데이터에 미친 영향을 추적한다.
입력 단계에서는 원천 데이터를 스냅샷 기준으로 수집하고 프로파일링을 실행한다. 처리 단계는 중복 제거, 스케일 정규화 선택, 로그·Box-Cox 변환, 검증 순서로 이어진다. 출력에는 정제 데이터셋뿐 아니라 스케일 파라미터, λ, 시프트, 적용 규칙을 메타데이터로 포함한다.
로그 입력에 음수가 있으면 시프트를 적용하고, 시프트할 수 없다면 변환을 건너뛰고 경보를 낸다. Box-Cox에 0 또는 음수가 있으면 최소값 보정 시프트를 사용하며, 실패하면 대체 변환을 적용한다. 중복 병합 중 충돌이 발생했을 때는 최신 타임스탬프나 신뢰도 점수 같은 우선순위 규칙으로 처리한다.
Python으로 구현하는 정제 단계
전제조건은 다음과 같다.
- Python 3.10+, pandas>=1.5, scikit-learn>=1.2, scipy>=1.9, numpy>=1.23
- 입력: pandas DataFrame df
결정적 키를 먼저 정규화한 뒤 중복을 제거하고, 같은 이메일 그룹 안에서 이름 유사도를 이용한 퍼지 매칭을 적용하는 예시다.
import pandas as pd
from rapidfuzz import fuzz, process # 퍼지 매칭 사용 시
# 1) 키 정규화
def norm_email(s: pd.Series) -> pd.Series:
return s.str.strip().str.lower().str.replace(r"\+.*(?=@)", "", regex=True)
def norm_phone(s: pd.Series) -> pd.Series:
return s.str.replace(r"\D", "", regex=True)
df["email_n"] = norm_email(df["email"])
df["phone_n"] = norm_phone(df["phone"])
# 2) 결정적 중복 제거
df = df.sort_values("updated_at").drop_duplicates(
subset=["customer_id", "email_n", "phone_n"], keep="last"
)
# 3) 퍼지 매칭(예: 이름 근접 매칭, 동일 이메일 내)
# 그룹 내 이름 유사도 90 이상을 하나로 묶는 간단 예시
def fuzzy_dedupe(group):
names = group["name"].tolist()
keep = set()
taken = set()
for n in names:
if n in taken:
continue
matches = process.extract(n, names, scorer=fuzz.token_sort_ratio, limit=None)
cluster = [m for m, score, _ in matches if score >= 90]
keep.add(min(cluster)) # 규칙 예: 사전순 가장 작은 이름 유지
taken.update(cluster)
return group[group["name"].isin(list(keep))]
df = df.groupby("email_n", group_keys=False).apply(fuzzy_dedupe)
스케일링과 분포 변환은 별도 컬럼에 적용하고, Box-Cox에서 얻은 λ와 시프트 값은 이후에도 사용할 수 있도록 보관한다.
import numpy as np
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from scipy.stats import boxcox
scale_cols = ["age", "tenure"]
skew_cols = ["amount", "income"]
# 1) 스케일링
minmax = MinMaxScaler()
zscaler = StandardScaler()
df[["age_mm","tenure_mm"]] = minmax.fit_transform(df[scale_cols])
df[["age_z","tenure_z"]] = zscaler.fit_transform(df[scale_cols])
# 2) 로그 변환(0 이하 처리: clip + log1p)
x = df[skew_cols].copy()
x = x.clip(lower=0)
df[[c + "_log1p" for c in skew_cols]] = np.log1p(x)
# 3) 박스콕스(양수화 시프트 + λ 저장)
bc_meta = {}
for c in skew_cols:
v = df[c].to_numpy()
shift = 0.0
if np.any(v <= 0):
shift = 1 - v.min()
v = v + shift
v_bc, lam = boxcox(v)
df[c + "_bc"] = v_bc
bc_meta[c] = {"lambda": float(lam), "shift": float(shift)}
# bc_meta를 모델 아티팩트로 저장하여 서빙 시 동일 변환 보장
변환 전후 분포는 KS-test, PSI로 비교하고 모델 성능의 AUC·MAE 변화를 함께 추적한다. 음수 로그나 과도한 스케일링을 차단하는 로직과 데이터 드리프트 경보도 검증 단계에 포함한다.
from scipy.stats import ks_2sample
# 변환 전후 분포 비교
stat, p = ks_2sample(df["amount"], df["amount_log1p"])
assert p < 0.05 # 분포가 유의미하게 달라졌음을 확인(예: 왜도 완화)
Min-Max의 min*, scale*, StandardScaler의 mean*, var*, Box-Cox의 λ·shift는 모델과 함께 버전 관리한다. 시드, 버전, 스키마 해시도 아티팩트로 보관해 재현성을 확보한다. 데이터베이스에서 중복을 제거할 때는 고유 인덱스와 UPSERT를 사용하고, 스냅샷 격리 수준에서 배치 처리하는 방식을 권장한다.
기법마다 달라지는 운영 특성
| 기법 | 성능(연산 비용) | 확장성 | 일관성(재현성) | 안정성(이상치 민감) | 운영 편의 |
|---|---|---|---|---|---|
| 중복 제거 | 중~상(퍼지 시 고) | 중(블로킹·샤딩 필요) | 중~상(결정적 키 우수) | 상(룰 기반) | 중(룰 관리 부담) |
| 정규화(Min-Max) | 상 | 상 | 상 | 하(이상치 영향 큼) | 상 |
| 표준화(Z-score) | 상 | 상 | 상 | 중(이상치 영향) | 상 |
| 로그/Box-Cox | 중 | 상 | 상(파라미터 고정 시) | 상(왜도 완화) | 중(시프트/λ 관리) |
CRM과 마케팅에서는 이메일·전화번호 중복을 제거해 고객 단일화(Single Customer View)를 만들고 캠페인 타게팅 정확도를 높일 수 있다. 추천·랭킹 모델은 수치 피처의 스케일을 맞춰 최적화 수렴을 안정화하고 학습 시간을 줄인다. 금융 리스크에서는 거래금액에 로그·Box-Cox 변환을 적용해 분포를 정상화하고 이상치 영향을 완화하며 점수카드 성능을 개선한다. 제조 품질에서는 센서 측정값의 표준화와 Box-Cox 변환으로 공정능력 지표를 안정화하고 경보 오탐률을 낮춘다.
퍼지 매칭은 정확도를 높이는 대신 비용과 지연을 늘린다. 따라서 결정적 키를 우선하고, 퍼지 매칭이 필요한 구간에는 블로킹 같은 전처 필터를 둔다. Min-Max는 이상치 영향을 크게 받으므로 Robust 또는 표준화를 대안으로 검토할 수 있다. 로그 변환은 해석이 쉽고, Box-Cox는 해석성은 낮아지지만 더 유연하게 변환 강도를 조절할 수 있다.
프로파일링, 전략 선택, 파라미터 추정, 검증, 버저닝을 고정 절차로 만들면 운영 일관성을 확보할 수 있다. 실패-안전 가드로 음수 로그를 막고 Box-Cox의 양수화를 강제하며, 품질 기준에 미달하면 롤백한다.
정량 효과로는 모델 성능(AUC/ROC/PR) 25%p 향상, 학습 수렴 에폭/시간 3050% 단축, 중복 제거 후 스토리지 1020% 절감, 경보 오탐률 1530% 감소가 제시된다. 데이터 신뢰성과 일관성을 높여 재현 가능한 ML 운영체계를 만들고, 규정 준수·감사 대응과 모델 디버깅 효율을 개선하는 효과도 기대할 수 있다.