bonggyulim 님의 블로그
ML - 데이터 전처리 본문
머신러닝에서 모델을 바꾸는 것만큼 중요한 것이 데이터 전처리다.
실제로 데이터를 다뤄보면 모델을 바로 학습하는 시간보다, 먼저 데이터를 확인하고 정리하고 가공하는 시간이 더 길다.
복잡한 수식 설명보다 실제로 어떻게 전처리를 하는지에 집중해서 정리했다.
예시는 seaborn의 tips 데이터셋을 사용했다.
1. 실습 준비
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler, MinMaxScaler
2. 데이터 불러오기
먼저 기본 데이터셋을 불러온다.

tips = sns.load_dataset("tips")
df = tips.copy()
df.head() # 데이터 앞부분 확인
3. 데이터 확인
전처리의 시작은 데이터를 확인하는 것이다.
컬럼 구성, 데이터 타입, 결측치 여부, 수치형 분포를 먼저 봐야 한다.

print("===== 데이터 정보 =====")
df.info()
# 수치형 컬럼의 기초 통계량 확인
print("===== 기초 통계량 =====")
df.describe()
# 컬럼명 확인
print("===== 컬럼 목록 =====")
df.columns
# 특정 컬럼 확인
df[["total_bill", "tip"]].head()
4. 결측치 확인과 처리
실무 데이터에서는 결측치가 자주 나온다.
기본 tips 데이터셋은 결측치가 거의 없기 때문에, 실습용으로 일부 값을 비워서 처리 과정을 확인해보겠다.
df.loc[0, "total_bill"] = np.nan
df.loc[3, "sex"] = np.nan
df.loc[5, "smoker"] = np.nan
4-1. 결측치 확인
isnull()은 결측치 여부를 확인하고, sum()을 붙이면 컬럼별 결측치 개수를 볼 수 있다.
df.isnull().sum() # 각 열의 결측치 개수 확인
![]() 결측지 제거전 |
![]() 결측치 제거 후 |
4-2. 수치형 결측치 처리
fillna()는 비어 있는 값을 다른 값으로 채울 때 사용하는 메서드다.
# 수치형 컬럼은 평균이나 중앙값으로 채우는 경우가 많다
df["total_bill"] = df["total_bill"].fillna(df["total_bill"].mean())
4-3. 범주형 결측치 처리
# 문자형 컬럼은 최빈값으로 채우는 경우가 많다
df["sex"] = df["sex"].fillna(df["sex"].mode()[0])
df["smoker"] = df["smoker"].fillna(df["smoker"].mode()[0])
4-4. 결측치 제거
fillna() 대신, 경우에 따라서는 dropna()로 결측치가 포함된 행을 제거할 수도 있다.
다만 데이터 손실이 발생할 수 있으므로 신중히 사용해야 한다.
df_dropna = df.dropna()
5. 중복 데이터 확인과 제거
같은 데이터가 여러 번 들어 있으면 학습에 왜곡이 생길 수 있다.
그래서 중복 여부도 확인해보는 편이 좋다.
5-1. 중복 확인
df.duplicated().sum()
5-2. 중복 제거
df = df.drop_duplicates()
6. 이상치 확인과 제거
이상치는 다른 값들에 비해 너무 크거나 작은 값이다.
이상치를 그대로 두면 평균, 분산, 회귀 계수 등에 영향을 줄 수 있다.
이번 예제에서는 IQR 기준으로 total_bill의 이상치를 확인한 뒤, 전처리 과정 예시를 보여주기 위해 일부 이상치를 제거해보았다. 이상치라고 보이는 값이 모두 잘못된 데이터는 아니기 때문에, 단순히 수치 기준만으로 바로 삭제하기보다 도메인 의미와 모델 목적을 함께 고려해서 판단해야 한다.
6-1. 박스플롯으로 이상치 확인
# 박스플롯으로 total_bill의 이상치를 먼저 확인한다.
plt.figure(figsize=(8, 4))
sns.boxplot(x=df["total_bill"])
plt.title("total_bill Boxplot")
plt.show()

quantile()은 분위수를 계산하는 메서드다.
IQR 방식은 이상치를 빠르게 확인할 때 많이 사용한다.
# total_bill 컬럼을 기준으로 IQR 방식으로 이상치를 찾는다.
Q1 = df["total_bill"].quantile(0.25)
Q3 = df["total_bill"].quantile(0.75)
IQR = Q3 - Q1
# 이상치 판단 경계값 설정
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 이상치만 따로 확인
outliers = df[(df["total_bill"] < lower_bound) | (df["total_bill"] > upper_bound)]
outliers.head()
6-3. 이상치 제거
이상치를 무조건 삭제하는 것이 정답은 아니다.
실제 중요한 이벤트일 수도 있으므로, 먼저 데이터 의미를 확인하는 것이 좋다.
df = df[(df["total_bill"] >= lower_bound) & (df["total_bill"] <= upper_bound)]
print("shape:", df.shape)
# 제거 후 다시 박스플롯 확인
plt.figure(figsize=(8, 4))
sns.boxplot(x=df["total_bill"])
plt.title("total_bill Boxplot After Outlier Removal")
plt.show()
제거 전 shape: (244, 7) -> 제거 후 shape: (235, 7)

7. 필요 없는 컬럼 제거
모든 컬럼이 항상 모델에 도움이 되는 것은 아니다.
식별자, 누수 가능성이 있는 값, 모델과 직접 관련 없는 값은 제거 대상이 될 수 있다.
예를 들어 불필요한 컬럼이 있다면 아래처럼 제거할 수 있다.
# 예시
# df = df.drop(columns=["id", "timestamp"])
8. 범주형 데이터 인코딩
문자열 형태의 범주형 컬럼은 많은 머신러닝 모델이 직접 처리하지 못하므로, 보통 숫자 형태로 변환한 뒤 사용한다.
8-1. 라벨 인코딩
라벨 인코딩은 범주형 값을 정수형으로 바꾸는 방식이다.
라벨 인코딩은 순서형 범주형 데이터에 더 적합하며, 분류 문제의 타깃값 y를 숫자로 바꿀 때도 자주 사용된다.
다만 입력값 X의 명목형 범주형 데이터에는 원-핫 인코딩이 더 적절한 경우가 많다.
# 라벨 인코딩은 범주형 값을 숫자로 바꾸는 방식이다.
# 원-핫 인코딩을 사용할 예정이므로, 여기서는 예시 확인용으로만 별도 복사본을 만들어 진행
df_label = df.copy()
le = LabelEncoder()
df_label["sex"] = le.fit_transform(df_label["sex"])
df_label["smoker"] = le.fit_transform(df_label["smoker"])
df_label["day"] = le.fit_transform(df_label["day"])
df_label["time"] = le.fit_transform(df_label["time"])
df_label.head()

8-2. 원-핫 인코딩
원-핫 인코딩은 주로 입력값 X의 범주형 변수를 변환할 때 사용한다.
회귀 모델, SVM, KNN처럼 문자열을 직접 처리하지 못하는 모델에서 자주 사용된다.
# 범주형 컬럼을 0/1 컬럼으로 바꾼다.
df = pd.get_dummies(
df,
columns=["sex", "smoker", "day", "time"],
drop_first=True
)

9. 입력값(X) / 타깃값(y) 분리
이제 예측 대상과 입력 데이터를 나눈다.
이번 예시에서는 tip을 예측 대상으로 두고, 나머지 컬럼을 입력값으로 사용한다.
X = df.drop(columns=["tip"])
y = df["tip"]
10. 다중공선성 확인 및 제거
전처리 단계에서는 feature들끼리 너무 비슷한 정보를 담고 있는지도 확인할 수 있다.
이런 경우를 다중공선성이라고 하고, 특히 선형 모델 계열에서 영향을 줄 수 있다.
10-1. 상관관계 히트맵 확인
# 수치형 컬럼끼리의 상관관계를 확인한다.
corr_matrix = X.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(
corr_matrix,
annot=True,
fmt=".2f",
cmap="coolwarm",
linewidths=0.5
)
plt.title("Feature Correlation Heatmap")
plt.show()

10-2. 상관관계 높은 컬럼 찾기
10-2. 상관관계 높은 컬럼 찾기
# 상관관계 행렬의 상삼각 영역만 사용한다.
upper_triangle = corr_matrix.where(
np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)
)
# 절댓값 기준 0.8보다 큰 컬럼을 찾는다.
high_corr_cols = [
column for column in upper_triangle.columns
if any(upper_triangle[column].abs() > 0.8)
]
print("상관관계가 높은 컬럼:", high_corr_cols)
10-3. 상관관계 높은 컬럼 제거
X_reduced = X.drop(columns=high_corr_cols)
X_reduced.head()
상관관계가 높은 컬럼은 무조건 제거하는 것이 아니라,
모델 특성과 데이터 의미를 같이 보고 판단하는 것이 좋다.
11. 학습 데이터 / 테스트 데이터 분리
전처리 후에는 학습용 데이터와 테스트용 데이터를 나눈다.
모델은 학습용 데이터로 학습하고, 테스트 데이터로 성능을 확인한다.
X_train, X_test, y_train, y_test = train_test_split(
X_reduced,
y,
test_size=0.2,
random_state=42
)
12. 수치형 컬럼만 스케일링
스케일링은 컬럼마다 값의 범위가 크게 다를 때 이를 맞춰주는 작업이다.
예를 들어 어떤 컬럼은 값이 1~5 사이인데, 다른 컬럼은 1000~10000 사이일 수 있다.
이런 상태로 모델을 학습하면 큰 값을 가지는 컬럼이 더 큰 영향을 주는 경우가 있다.
특히 거리 기반 모델이나 선형 모델, 그리고 경사하강법 기반 학습에서는 스케일 차이가 성능과 학습 안정성에 영향을 줄 수 있다.
반면 트리 계열 모델은 보통 스케일링 영향을 크게 받지 않는다.
주의할점은 fit_transform()은 학습 데이터에만 적용하고, 테스트 데이터에는 transform()만 적용해야 한다는 점이다.
12-1. 수치형 컬럼 지정
numeric_cols = ["total_bill", "size"]
12-2. StandardScaler 적용
StandardScaler는 각 수치형 컬럼을 평균 0, 표준편차 1 기준으로 변환하는 방식이다.
이미지 픽셀값처럼 원래 범위가 분명한 데이터나, 입력값을 일정 구간으로 맞추고 싶은 경우에 자주 사용
X_train_standard = X_train.copy()
X_test_standard = X_test.copy()
standard_scaler = StandardScaler()
X_train_standard[numeric_cols] = standard_scaler.fit_transform(X_train_standard[numeric_cols])
X_test_standard[numeric_cols] = standard_scaler.transform(X_test_standard[numeric_cols])
X_train_standard.head()
12-3. MinMaxScaler 적용
MinMaxScaler는 각 수치형 컬럼의 값을 0과 1 사이 범위로 변환하는 방식이다.
로지스틱 회귀, 선형 회귀, SVM, KNN, PCA처럼 스케일에 민감한 모델에서 자주 사용
X_train_minmax = X_train.copy()
X_test_minmax = X_test.copy()
minmax_scaler = MinMaxScaler()
X_train_minmax[numeric_cols] = minmax_scaler.fit_transform(X_train_minmax[numeric_cols])
X_test_minmax[numeric_cols] = minmax_scaler.transform(X_test_minmax[numeric_cols])
X_train_minmax.head()
13. 데이터 확인 시 자주 사용하는 시각화 방법
전처리를 시작하기 전에는 단순히 head(), info(), describe()만 보는 것보다,
시각화를 통해 데이터 분포와 관계를 함께 확인하는 것이 훨씬 도움이 된다.
특히 시각화는 다음과 같은 상황에서 유용하다.
- 수치형 컬럼의 분포 확인
- 범주형 컬럼의 빈도 확인
- 이상치 확인
- 변수 간 관계 확인
- 상관관계 확인
이번에는 tips 데이터셋 기준으로 자주 사용하는 기본 시각화 방법을 정리해보겠다.
13-1. 히스토그램(Histogram)
히스토그램은 수치형 데이터가 어떤 구간에 많이 몰려 있는지 확인할 때 사용한다.
데이터가 한쪽으로 치우쳐 있는지, 대략적인 분포가 어떤지 파악할 수 있다.
plt.figure(figsize=(8, 4))
sns.histplot(df["total_bill"], kde=True)
plt.title("total_bill Distribution")
plt.show()

13-2. 카운트플롯(Countplot)
카운트플롯은 범주형 데이터의 개수를 시각적으로 확인할 때 사용한다.
각 범주가 얼마나 자주 등장하는지 한눈에 볼 수 있다.
plt.figure(figsize=(8, 4))
sns.countplot(x=df["day"])
plt.title("Count of day")
plt.show()

13-3. 박스플롯(Boxplot)
박스플롯은 수치형 데이터의 분포와 이상치를 함께 확인할 때 자주 사용한다.
사분위수 범위와 중앙값, 그리고 이상치로 의심되는 값을 한 번에 볼 수 있다.

13-4. 산점도(Scatterplot)
산점도는 두 수치형 변수 사이의 관계를 확인할 때 사용한다.
특정 변수끼리 함께 증가하는지, 감소하는지, 또는 뚜렷한 패턴이 있는지 확인할 수 있다.

전처리는 단순히 데이터를 정리하는 과정이 아니라,
모델이 학습하기 좋은 형태로 데이터를 바꾸고 불필요한 노이즈를 줄이는 과정이라는 점에서 매우 중요하다.
이번 글에서는 전처리 자체에 초점을 맞춰 흐름을 정리했고,
다음 글에서는 이렇게 준비한 데이터를 바탕으로 모델 학습 결과를 어떻게 평가하는지, 그리고 회귀 성능 지표를 어떻게 해석해야 하는지를 정리해볼 예정이다.
'AI dev > Machine Learning' 카테고리의 다른 글
| 추가로 알아두면 좋은 전처리: PCA, Vectorization, SMOTE (0) | 2026.04.07 |
|---|---|
| ML - 모델 학습 및 평가 (0) | 2026.04.04 |

