데이터 전처리 (Data Preprocessing)
데이터 전처리는 머신러닝 모델을 훈련하기 전에 데이터를 준비하는 과정이다.
전처리를 거치지 않은 원시(raw) 데이터는 머신러닝 모델 학습에 적합한 형태로 되어 있지 않기 때문에 데이터 전처리 과정이 필요하다.
이 과정은 데이터를 분석하고 정제하여 모델이 이해할 수 있는 형태로 변환하는 것을 포함한다.
데이터마다 특성이 다르기 때문에 데이터 전처리 과정은 데이터마다 다르지만 보통 다음과 같은 과정을 따른다.
- 데이터 수집 (Data Collection):
- 프로젝트 목적에 맞는 데이터를 수집한다. 이는 주로 데이터베이스, 파일, API, 웹 크롤링 등을 통해 이루어진다.
- 데이터의 양과 품질을 고려하여 데이터를 수집하고 필요한 경우 데이터를 정제한다.
- 데이터 탐색 및 이해 (Exploratory Data Analysis):
- 수집한 데이터를 탐색하여 데이터의 구조, 특성, 그리고 분포를 이해한다.
- 통계적 분석 및 시각화를 사용하여 데이터의 패턴을 파악하고 잠재적인 문제를 식별한다.
- 결측치 처리 (Handling Missing Data):
- 데이터에 결측치가 존재하는 경우, 이를 처리한다. 결측치는 NaN(Not a Number) 또는 빈 값으로 표시될 수 있다.
- 결측치를 채우거나 삭제하여 데이터의 완전성을 높인다. 결측치를 채울 땐 평균값이나 중앙값, 최빈값 등을 사용할 수 있다.
- 이상치 제거 (Outlier Removal):
- 데이터에서 이상치는 일반적인 데이터 패턴에서 벗어나는 값이다. 이상치는 모델의 성능을 저하시킬 수 있으므로 제거 또는 대체해야 한다.
- 이상치를 식별하고 해당 행이나 열을 삭제하거나 다른 값으로 대체한다.
- 데이터 스케일링 (Scaling):
- 데이터 스케일링은 데이터의 크기를 조정하는 과정이다. 일반적으로는 표준화(z-score normalization) 또는 정규화(min-max scaling)을 사용힌다.
- 이를 통해 데이터의 범위를 조정하고 모델의 성능을 향상시킨다.
- 범주형 데이터 인코딩 (Categorical Data Encoding):
- 범주형 변수는 문자열 또는 정수 값으로 표현되며, 모델에 직접 입력될 수 없다. 따라서 범주형 변수를 숫자형으로 변환해야 한다.
- 일반적으로는 원-핫 인코딩 또는 레이블 인코딩을 사용하여 범주형 변수를 변환한다.
- 특성 선택 및 추출 (Feature Selection and Extraction):
- 모델 학습에 가장 유용한 특성을 선택하거나 새로운 특성을 추출하여 모델의 성능을 향상시킨다.
- 이를 통해 모델의 복잡성을 줄이고 과적합을 방지할 수 있다.
- 데이터 정규화 (Data Normalization):
- 데이터 정규화는 데이터의 분포를 조정하여 모델의 학습을 안정화시키는 과정이다.
- 주로 신경망 모델에서 사용되며, 평균을 0으로, 표준 편차를 1로 만들어 데이터를 정규분포에 가깝게 만든다.
- 데이터 변환 (Data Transformation):
- 데이터를 변환하여 새로운 정보를 추출하거나 모델이 더 잘 이해할 수 있도록 한다. 예를 들어, 텍스트 데이터의 토큰화 또는 이미지 데이터의 전처리가 여기에 해당된다.
- 이를 통해 원본 데이터에서 유용한 특성을 추출하거나 데이터를 적절한 형태로 변환하여 모델의 성능을 향상시킨다.
이러한 데이터 전처리 단계는 모델의 성능을 향상시키고 안정성을 확보하는 데 중요한 역할을 한다.
이제 몇 가지 방법을 파이썬 코드를 통해 알아보자.
결측치 처리 방법 예시 (Handling Missing Data)
주피터랩으로 먼저 가상 데이터를 하나 만들자.
import pandas as pd
# 예시용 데이터프레임 생성
data = pd.DataFrame({
'A': [ 1, 2, None, 4, 5],
'B': [None, 20, 30, 40, 50],
'C': [ 100, 200, 300, 400, 500]
})
아래 코드로 방금 만든 데이터를 출력해보자.
print("예시용 데이터프레임:\n", data)
예시용 데이터프레임:
A B C
0 1.0 NaN 100
1 2.0 20.0 200
2 NaN 30.0 300
3 4.0 40.0 400
4 5.0 50.0 500
이제 이 데이터를 가지고 결측치를 채워넣는 네 가지 방법을 알아보자.
1. 결측치가 있는 행 삭제
결측치를 그냥 삭제하는 경우에는 다음과 같은 상황에서 사용될 수 있다.
- 결측치가 적은 경우:
- 데이터셋에서 결측치가 일부분이며, 전체 데이터에 비해 상대적으로 적은 비율을 차지하는 경우에 사용될 수 있다.
- 결측치의 비율이 매우 적기 때문에 데이터의 손실이 크지 않다고 판단되는 경우에 결측치를 그냥 삭제할 수 있다.
- 결측치가 무작위로 발생한 경우:
- 결측치가 데이터의 특정 패턴에 따라 발생하는 것이 아니라, 무작위로 발생한 경우에는 결측치를 그냥 삭제하는 것이 타당할 수 있다.
- 이 경우, 데이터셋에서 결측치를 삭제하더라도 데이터의 편향이 크게 발생하지 않을 수 있다.
- 분석 목적에 결측치가 미치는 영향이 크지 않은 경우:
- 분석하려는 변수에 결측치가 있더라도, 해당 변수가 분석 목적에 큰 영향을 미치지 않는 경우에는 결측치를 그냥 삭제할 수 있다.
- 예를 들어, 분석하려는 변수가 주요 관심사가 아니거나, 다른 변수들로 충분히 설명될 수 있는 경우에는 결측치를 삭제해도 분석 결과에 큰 영향을 미치지 않을 수 있다.
# 결측치가 있는 행 삭제
data_dropped = data.dropna()
print("결측치 삭제 결과:\n", data_dropped)
결측치 삭제 결과:
A B C
1 2.0 20.0 200
3 4.0 40.0 400
4 5.0 50.0 500
2. 평균값, 중앙값, 최빈값으로 채우기
평균값, 중앙값, 최빈값으로 결측치를 채우는 상황은 주로 수치형 데이터에서 발생한다. 이러한 상황에서는 다음과 같은 경우에 해당 방법을 사용할 수 있다:
- 평균값으로 결측치를 채우는 경우:
- 데이터의 평균적인 값을 유지하면서 결측치를 대체할 때 사용된다.
- 예를 들어, 학생들의 시험 점수 데이터에서 결측치가 발생한 경우, 전체 학생들의 평균 점수로 결측치를 대체할 수 있다.
- 중앙값으로 결측치를 채우는 경우:
- 데이터의 중간값을 사용하여 이상치의 영향을 줄이고 결측치를 대체할 때 사용된다.
- 예를 들어, 소득 데이터에서 이상치가 있는 경우, 중앙값을 사용하여 결측치를 대체할 수 있다.
- 최빈값으로 결측치를 채우는 경우:
- 범주형 데이터에서 결측치를 대체할 때 사용된다.
- 예를 들어, 고객의 성별 데이터에서 결측치가 발생한 경우, 가장 많이 등장하는 성별로 결측치를 대체할 수 있다.
이러한 방법은 데이터의 분포를 유지하면서 결측치를 대체할 수 있어서 데이터의 왜곡을 최소화하는 데 도움이 된다.
# 결측치를 평균값으로 채우기
data_filled_mean = data.fillna(data.mean())
print("\n평균값으로 결측치 채우기:\n", data_filled_mean)
# 결측치를 중앙값으로 채우기
data_filled_median = data.fillna(data.median())
print("\n중앙값으로 결측치 채우기:\n", data_filled_median)
# 결측치를 최빈값으로 채우기
data_filled_mode = data.apply(lambda x: x.fillna(x.mode()[0]))
print("\n최빈값으로 결측치 채우기:\n", data_filled_mode)
평균값으로 결측치 채우기:
A B C
0 1.0 35.0 100
1 2.0 20.0 200
2 3.0 30.0 300
3 4.0 40.0 400
4 5.0 50.0 500
중앙값으로 결측치 채우기:
A B C
0 1.0 35.0 100
1 2.0 20.0 200
2 3.0 30.0 300
3 4.0 40.0 400
4 5.0 50.0 500
최빈값으로 결측치 채우기:
A B C
0 1.0 20.0 100
1 2.0 20.0 200
2 1.0 30.0 300
3 4.0 40.0 400
4 5.0 50.0 500
3. 앞의 값이나 뒤의 값으로 결측치 채우기
앞이나 뒤의 값으로 결측치를 채우는 경우는 주로 시계열 데이터에서 발생한다. 다음과 같은 상황에서 해당 방법이 유용할 수 있다.
- 시간에 따라 데이터가 변화하는 경향이 있는 경우:
- 시계열 데이터에서는 시간이 지남에 따라 데이터가 변화하는 경향이 있다. 이러한 상황에서는 이전 값이나 다음 값으로 결측치를 채워넣는 것이 합리적일 수 있다.
- 연속적인 공간 데이터에서의 결측치 채우기:
- 공간적인 데이터에서도 연속성을 유지하는 것이 중요할 때가 있다. 이전 위치의 값이나 다음 위치의 값으로 결측치를 채워넣는 것이 데이터의 연속성을 유지하는 데 도움이 될 수 있다.
- 데이터의 추세를 유지하면서 결측치를 대체해야 할 때:
- 데이터의 추세가 일정하다고 가정할 때, 이전 값이나 다음 값으로 결측치를 채워넣으면 데이터의 일관성을 유지할 수 있다.
이러한 경우에는 이전 값이나 다음 값으로 결측치를 채워넣는 것이 데이터의 연속성을 유지하면서 결측치를 채우는 효과적인 방법일 수 있다.
# 앞의 값으로 결측치 채우기 (Forward fill)
data_ffill = data.ffill()
print("\n앞의 값으로 결측치 채우기:\n", data_ffill)
# 뒤의 값으로 결측치 채우기 (Backward fill)
data_bfill = data.bfill()
print("\n뒤의 값으로 결측치 채우기:\n", data_bfill)
앞의 값으로 결측치 채우기:
A B C
0 1.0 NaN 100
1 2.0 20.0 200
2 2.0 30.0 300
3 4.0 40.0 400
4 5.0 50.0 500
뒤의 값으로 결측치 채우기:
A B C
0 1.0 20.0 100
1 2.0 20.0 200
2 4.0 30.0 300
3 4.0 40.0 400
4 5.0 50.0 500
유의할 점은 첫 번째 경우처럼 앞이나 뒤의 값이 없으면 결측치가 채워지지 않는다.
4. 선형 보간을 사용하여 결측치 채우기
선형 보간을 사용하는 경우에는 3번처럼 시계열 데이터에서 발생한다. 다음과 같은 상황에서 선형 보간이 유용하게 활용될 수 있다.
- 연속적인 데이터에서 발생한 결측치를 대체하는 경우:
- 시계열 데이터에서는 연속적인 시간 간격으로 데이터가 기록되기 때문에, 시간에 따라 데이터의 값이 변화하는 추세를 가진다. 이때 발생한 결측치를 선형 보간을 사용하여 대체할 수 있다.
- 이미지나 음성 데이터와 같은 연속적인 신호 데이터에서의 보간:
- 이미지나 음성 데이터와 같은 연속적인 신호 데이터에서는 시간적인 연속성이 중요하다. 이러한 데이터에서 발생한 결측치를 선형 보간을 사용하여 대체할 수 있다.
- 연속적인 공간 데이터에서의 보간:
- 지도 데이터나 공간 분포를 나타내는 데이터에서도 선형 보간을 사용하여 연속적인 공간상의 결측치를 대체할 수 있다.
선형 보간은 이전 데이터 포인트와 다음 데이터 포인트 사이의 직선을 사용하여 결측치를 추정하기 때문에, 데이터가 일정한 추세를 가지고 있는 경우에 유용하게 활용될 수 있다.
# 선형 보간을 사용하여 결측치 채우기
data_interpolated = data.interpolate(method='linear')
print("\n선형 보간을 사용하여 결측치 채우기:\n", data_interpolated)
선형 보간을 사용하여 결측치 채우기:
A B C
0 1.0 NaN 100
1 2.0 20.0 200
2 3.0 30.0 300
3 4.0 40.0 400
4 5.0 50.0 500
위의 결과에서 B 열의 0번째 요소가 여전히 NaN임을 확인할 수 있다.
선형 보간을 사용하여 결측치를 채울 때, B 열의 0번째 요소가 여전히 NaN으로 남아있는 이유는 데이터의 첫 번째 값이 NaN이기 때문이다. 선형 보간은 결측치를 주변의 값들을 이용하여 채워넣는데, 첫 번째 값이 결측치인 경우에는 앞쪽에 채울 값이 없어서 결측치를 그대로 둔 채로 처리된다. 따라서 B 열의 0번째 요소는 선형 보간이 적용되지 않고 NaN으로 남게 된다.
따라서 열의 첫 번째 값이 NaN인지 확인한 다음 선형 보간을 사용해야 한다.
데이터 스케일링 예시 (Data Scaling)
데이터 스케일링은 데이터의 범위를 조정하여 모델의 성능을 개선하거나 수렴 속도를 높이는 데 사용된다. 주요 목표는 데이터의 특성을 비교적 비슷한 범위로 맞추는 것이다. 주요한 데이터 스케일링 방법으로는 표준화(Standardization)와 정규화(Normalization)가 있다.
표준화(Standardization)
표준화는 데이터를 평균이 0이고 표준편차가 1인 분포로 변환하는 방법이다. 주로 평균과 표준편차를 이용하여 변환한다.
수식으로 나타내면 다음과 같다.
$$ z = (x - μ) / σ $$
여기서, \(z\)는 표준화된 값, \(x\)는 원래 값, \(μ\)는 평균, \(σ\)는 표준 편차이다.
정규화(Normalization)
정규화는 데이터의 범위를 [0, 1] 또는 [-1, 1]로 변환하는 방법입니다. 주로 최솟값과 최댓값을 이용하여 변환한다.
수식으로 나타내면 다음과 같다.
$$ x_{norm} = (x - x_{min}) / (x_{max} - x_{min}) $$
여기서, \(x_{\text{norm}}\)은 정규화된 값, \(x\)는 원래 값, \(x_{\text{min}}\)은 최솟값, \(x_{\text{max}}\)는 최댓값이다.
아래 코드를 통해 데이터 스케일링을 살펴보자.
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
# 예시 데이터 생성
data = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]])
# 표준화
scaler = StandardScaler()
data_standardized = scaler.fit_transform(data)
print("표준화된 데이터:\n", data_standardized)
# 정규화
min_max_scaler = MinMaxScaler()
data_normalized = min_max_scaler.fit_transform(data)
print("정규화된 데이터:\n", data_normalized)
표준화된 데이터:
[[-1.22474487 -1.22474487 -1.22474487]
[ 0. 0. 0. ]
[ 1.22474487 1.22474487 1.22474487]]
정규화된 데이터:
[[0. 0. 0. ]
[0.5 0.5 0.5]
[1. 1. 1. ]]
scikit-learn에서 제공하는 함수를 사용하면 간단하게 스케일링이 가능하다.