🎯 데이터 전처리 과정 총정리

2024. 10. 7. 15:23개발/💠 Alchemist

 

 

 

AIchemist 다음주 실습은 데이터 전처리 과정에 초점을 맞출 생각이다

그래서 나도 데이터 전처리 과정을 다시 복습해야겠움...

 

물론 전처리 과정은 데이터의 형태, 데이터를 학습하는 모델의 목적 등에 따라  매우 달라질 것이다

그래서 이 글은 데이터 전처리 과정을 획일화하고자 함이 아니라 

전처리 과정에서 할 수 있는 선택들을 잘 정리해놓기 위함이다

 

 

 

데이터 전처리의 흐름

1.  데이터의 전체적인 정보 확인

2.  null값 처리

3.  칼럼 분리 및 전처리 - 독립변수, 종속변수(타겟값) / 정형, 비정형
     1 ) 타겟값 칼럼 형태 보고 예측 모델 선정 -> 분류 or 회귀
     2 ) 정형 : 스케일링, 정규화 / 비정형 : 인코딩 / (정형 데이터의 카테고리화)

4.  데이터 이상치 처리

5.  학습, 예측 데이터 분리

 

 

 

1.   데이터의 전체적인 정보 확인

 

데이터의 전처리 과정을 가장 좌지우지하는 것은 아무래도 데이터의 형태이기 때문에

데이터의 전체적인 정보와 형태를 가장 먼저 확인해주는 작업이 필요하다

 

1 )  head

데이터세트의 row를 직접 확인할 수 있는 메서드 (default : row 5개)

dataset.head()

 

괄호 안에 보고 싶은 row의 개수 입력

 

2 )  shape

데이터세트의 형태를 (레코드 수, 칼럼 수) 형식으로 확인할 수 있는 메서드

dataset.shape

 

3 )  info

데이터세트의 칼럼, 데이터타입, 레코드 수, 칼럼별 not-null 레코드 수, 데이터타입별 칼럼 수 확인 가능

dataset.info()

info 함수를 통해서 데이터세트의 전체적인 구조를 확인할 수 있다

 

나는 보통 info의 결과를 통해서

( 1 ) 필요없는 칼럼 확인  ( 2 ) 칼럼별 데이터타입 확인 -> 정형, 비정형 칼럼 구분  ( 3 ) null 값 확인

이 세가지를 수행한다

 

* float, int => 정형 칼럼 / object => 비정형 칼럼

* 전체 레코드가 891인데 not-null count가 204인 경우 해당 칼럼에 null 값이 ( 891 - 204 = 687 )687개 있다는 의미다

 

4 )  describe

데이터세트의 통계량 확인하는 메서드  -> 칼럼별 레코드 수, 평균, 최대/최소값, 분포 

dataset.describe()

 

5 )  columns

데이터세트의 칼럼들을 확인하는 메서드

dataset.columns

 

6 )  dtypes / select_dtypes

dtypes : 모든 칼럼의 데이터타입 출력하는 메서드

dataset.dtypes

 

select_dtypes : 특정 데이터타입의 칼럼을 출력하는 메서드

dataset.select_dtypes(object)
dataset.select_dtypes(int)

int 타입의 칼럼들 조회한 결과

 

7 )  unique / nunique

 

unique : 데이터의 고유값 출력 (비정형 데이터)

dataset['고유값 확인하고 싶은 칼럼 이름'].unique()

 

nunique : 데이터의 고유값의 개수 출력

dataset['고유값 확인하고 싶은 칼럼 이름'].nunique()

 

8 )  value_counts

value_counts : 데이터의 레이블별 데이터 개수 출력

dataset['레이블별 데이터 개수 알고 싶은 칼럼명'].value_counts()

 

 

 

2.   null 값 처리

 

1 )  null 값 확인

 

null값의 존재 여부는 info를 통해 확인할 수 있다

' RangeIndex : 891 entries ' 를 통해 전체 레코드가 891개임을 확인할 수 있다

이때 칼럼별로 Non-Null Count,  즉 칼럼별 null이 아닌 레코드가 몇개인지를 확인한 뒤

이를 전체 레코드 수에서 제하면 칼럼별로 null인 레코드가 몇개인지 확인 가능하다

 

예를 들어 Age 칼럼의 경우에는 (891 - 714 = 177) 177개의 null값이 있고

Cabin 칼럼의 경우 (891-204 = 687) 687개의 null값이 있는 것이다

 

이를 통해 칼럼별로 전체 레코드 대비 null값의 개수를 확인할 수 있고 그에 따라 null값의 처리 방식을 달리 할 수 있다

 

2 )  null 값 처리

 

( 1 ) 칼럼 drop

특정 칼럼에 null 값이 너무 많은 경우에는 차라리 칼럼 하나를 완전히 삭제해버리는 것이 낫다

위의 Cabin 칼럼과 같이 null 값이 전체 레코드의 75% 이상 차지하는 경우, 칼럼을 drop해버리는 게 성능상 더 이득이다

dataset.drop(['Cabin'], axis = 1, inplace = True)
axis = 1은 열을, axis = 0은 행을 의미한다
우리는 'Cabin'이라는 열을 삭제하기 때문에 drop의 기준을 axis=1(즉, 열이 기준)으로 잡고 drop을 수행한다

inplace = True 는 원본 데이터세트에 변경된 데이터세트(Cabin이 삭제된)를 덮어씌운다는 의미다

 

( 2 )  레코드 drop

특정 칼럼에 null이 그다지 많지 않은 경우 두가지 선택을 할 수 있다

해당 레코드를 삭제하거나, 해당 레코드의 특정 칼럼값을 평균값으로 대체하거나

 

나같은 경우 특정 칼럼이 주요 칼럼인 경우,

( 즉 결과값에 영향을 많이 미치는 칼럼이라거나, 아예 타겟값 칼럼이거나 )

해당 레코드를 삭제한다

dataset.drop(5, axis=0, inplace=True)
이번에는 '열'을 삭제하는 것이 아닌 '행'을 삭제하는 것이기 때문에
axis = 0으로 둔다

 

( 3 )  평균값으로 대체

반면에 null값이 존재하는 칼럼이 전체적인 결과에 큰 영향이 없을 경우,

혹은 데이터양 자체가 너무 적어서 레코드 하나하나가 너무 소중한 경우

null 값을 평균값으로 대체한다(숫자형 칼럼의 경우)

dataset['Age'].fillna(dataset['Age'].mean(), inplace = True)
여기서 fillna()가 null값을 특정 값으로 대체해서 채울 때 사용하는 메서드이고
fillna 안에 들어있는 dataset['Age'].mean()은 Age 칼럼의 평균값을 의미한다
즉, 'Age' 칼럼의 null값을 Age 칼럼의 평균값으로 대체해주는 코드다

 

 

 

3.   데이터의 이상치 처리

 

1 )  이상치 확인

 

데이터의 이상치는 말그대로 이상한 데이터를 의미한다

예를 들어 사람의 몸무게가 12000000kg으로 되어 있다던가, 나이가 음수값으로 되어 있는 등의 경우가 있을 것이다

또한 정형데이터의 경우 데이터의 분포와 현저히 동떨어진 데이터를 이상치로 취급하기도 한다

 

 

2 )  이상치 처리

 

정형 데이터의 이상치 처리는 앞서 설명한 null 값 처리와 비슷하게

상황에 맞춰 레코드를 삭제하거나, 평균값으로 대체해주면 된다

 

Q1 = dataset['이상치 처리할 칼럼'].quantile(0.25)  # 1사분위수 (25%)
Q3 = dataset['이상치 처리할 칼럼'].quantile(0.75)  # 3사분위수 (75%)
IQR = Q3 - Q1  # IQR 계산

# IQR 범위를 벗어난 값(이상치) 제거
dataset = dataset[~((dataset['이상치 처리할 칼럼'] < (Q1 - 1.5 * IQR)) | (dataset['이상치 처리할 칼럼'] > (Q3 + 1.5 * IQR)))]

# 이상치를 평균값으로 대체
mean_value = dataset['이상치 처리할 칼럼'].mean()
dataset['이상치 처리할 칼럼'] = dataset['이상치 처리할 칼럼'].apply(lambda x: mean_value if x < lower_bound or x > upper_bound else x)
위 코드는 IQR 방식으로 이상치를 감지하고 처리하는 내용이다

Q1 : 전체 분포의 25% 구간
Q3 : 전체 분포의 75% 구간
Q1 ~ Q3(전체 분포의 25% ~ 75% 구간)을 정상 수치로 보고
나머지 구간을 삭제 혹은 평균값으로 대체해주는 코드다

 

하지만, 이상치 처리를 null 값 처리와 같이 타이트하게 하는 것은 오히려 성능 저하의 원인이 될 수 있다는 점을 기억해야 한다

이상치는 오류로 인한 말도 안 되는 값일 수도 있지만, 예상치 못한 변수의 상황일 수도 있다

이 때문에 데이터세트에서 변수를 완전히 제거하는 것은 성능 저하로 이어질 수 있다 

 

 

 

4.   칼럼 분리 및 처리

 

1 )  칼럼 분리

 

( 1 )  피처(독립변수), 타겟(종속변수) 칼럼 분리

분류나 회귀와 같은 지도학습의 경우 데이터세트 내에는 '문제'와 '답지'가 존재한다

답지란 모델이 예측해내야 하는 타겟값 칼럼(종속변수)을 의미하고

문제란 모델이 타겟값을 예측하는 데에 사용하는 나머지 칼럼들(독립변수들, 피처 칼럼)을 의미한다

 

원본 데이터세트는 이 답지와 문제가 합쳐져 있기 때문에 이를 분리해줘야 한다

# target / feature 칼럼 각각 y_target, X_features에 저장
y_target = dataset['타겟 칼럼']
X_features = house_df_ohe.drop('타겟 칼럼', axis=1, inplace=False)
여기서 y_target이 답지, X_features가 문제가 된다

 

( 2 )  정형, 비정형 분리

정형 데이터와 비정형 데이터의 처리 과정에 차이가 있기 때문에 둘을 분리한 뒤 각각 전처리를 진행해줘야 한다

데이터세트의 칼럼이 각각 정형인지, 비정형인지 확인하기 위해서는 위에서 봤던 info 메서드를 사용해주면 된다

여기서 int64, float64가 정형 데이터 칼럼들

object가 비정형 데이터 칼럼들이 된다

 

structured_cols = dataset.select_dtypes(include=['int64', 'float64'])
unstructured_cols = dataset.select_dtypes(include=['object'])

 

2 )  칼럼 처리

 

( 1 )  정형 데이터 칼럼 : 피처 스케일링

정형 데이터 칼럼은 피처 스케일링을 거쳐야 한다

 

그 전에 시각화를 통해 칼럼의 데이터 분포를 살펴봐 주는 것이 큰 도움이 된다

보통 스케일링 과정 전후로 칼럼들의 분포를 시각화하여 비교하는 과정을 거친다

이때 시각화에 사용되는 게 histogram인데

요새는 기술이 좋아져서 sweetviz라는 걸 install 해주면 알아서 시각화해주더라(은소마 알려줘서 고마워)

!pip install --upgrade sweetviz
import sweetviz as sv
import pandas as pd

# Sweetviz 보고서 생성
report = sv.analyze(dataset, pairwise_analysis='off')
# 보고서 HTML 파일로 저장
report.show_html('sweetviz_report.html')

 

피처 스케일링은 정규화와 표준화가 있는데 

간략하게 설명하자면 표준화는 데이터를 정규 분포로, 즉 평균을 0으로 만들고 표준편차를 1로 맞추는 작업을 말하고

정규화는 데이터의 범위를 특정 구간으로 맞추는 작업을 말한다

 

표준화에 사용되는 방법

- StandardScaler : 평균이 0이고 표준편차가 1인 정규 분포로 데이터를 변환

- RobustScaler : 중앙값(median)과 IQR(사분위 범위)을 사용하여 표준화, 이상치에 덜 민감함

 

정규화에 사용되는 방법

- MinMaxScaler : 데이터를 최소값 0과 최대값 1 사이로 변환

- MaxAbsScaler : 각 데이터 값을 절대값 기준으로 -1에서 1 사이로 조정

- 로그변환 : 로그를 취해 비대칭적인 데이터를 보다 대칭적으로 변환

- Normalizer : 벡터의 크기를 1로 만들기 위해 각 데이터 포인트를 유클리드 거리(L2 norm)로 정규화

 

왜곡이 너무 심한 칼럼에는 주로 로그 변환이 사용된다고 한다

 

# 칼럼 하나 로그 변환
log_column = np.log1p(dataset['스케일링할 칼럼'])

# 여러 칼럼들 로그 변환
log_columns = dataset['스케일링할 칼럼 리스트'].apply(np.log1p)

 

( 2 )  비정형 데이터 칼럼 : 인코딩

사이킷런의 머신러닝 알고리즘은 문자열 값을 입력값으로 허용하지 않기 때문에 비정형 칼럼의 경우 우선 정형화를 해줘야 한다 

즉, 인코딩을 수행해야 한다

 

비정형 데이터는 카테고리형 데이터와 텍스트 데이터로 나뉘게 되는데

카테고리형 데이터는 숫자 코드로 변환해주면 되고

텍스트 데이터는 피처 벡터화 등의 텍스트 분석 기법을 통해 전처리를 수행해줘야 한다

우선 텍스트 분석은 미뤄두고 카테고리형 데이터의 전처리를 살펴보자

 

'Sex' 칼럼을 예로 들자면

이 칼럼의 레이블인 'Male'과 'Female'을 각각 ML 알고리즘이 이해할 수 있는 0과 1로 우선 인코딩해줘야 한다

이 과정을 레이블 인코딩이라고 한다

<레이블 인코딩 전>

  Sex
데이터 1 'Female'
데이터 2 'Male'
데이터 3 'Female'

 

<레이블 인코딩 후>

  Sex
데이터 1 1
데이터 2 0
데이터 3 1

 

하지만 레이블 인코딩의 경우 숫자값의 크고 작음에 대한 특성이 모델에 적용될 위험이 있다

따라서 주로 원-핫 인코딩을 사용하는데 이러면 아래와 같이 인코딩된다

<원-핫 인코딩 후>

  Male Female
데이터 1 0 1
데이터 2 1 0
데이터 3 0 1

이런 식으로 되면 숫자의 크고 작음을 적용시키지 않으면서 인코딩이 가능하다

 

get_dummies() : 원-핫 인코딩 적용하는 메서드

pd.get_dummies('원-핫 인코딩 적용하고 싶은 칼럼 리스트')

 

 

( 3 )  정형 데이터 칼럼 : 카테고리화 

어떤 경우에는 정형 데이터 칼럼을 구간별로 나눠서 카테고리화하는 경우가 있다

이건 상황을 보고 너무 구체적인 숫자 타입보다 카테고리화했을 때 더 적절하다고 판단되면 수행하면 된다

 

'Age' 칼럼을 예로 들자면

구체적인 나이 데이터를 사용하지 않고 0~13세를 '어린이', 14~24세를 '청소년', 25~35세를 '청년' 등으로 나누는 것이다

 

 

 

5.   학습, 예측 데이터 분리

마지막으로 모델에 데이터를 넣기 전에 수행해야 하는 작업은 학습, 예측 데이터 분리다

X_train, X_test, y_train, y_test = train_test_split(X_features, y_target, test_size=0.2, random_state=42)

앞서서 X_features을 문제 칼럼, y_target을 답지 칼럼으로 분리했었다

이를 다시 학습 / 예측 데이터로 분리해줘야 한다

- X_train : 학습에 사용할 문제 칼럼

- y_train : X_train의 답지

- X_test : 예측에 사용할 문제 칼럼

- y_test : X_test의 답지

 

지도 학습의 경우 모델을 학습시킬 땐 문제(X_train)와 답지(y_train) 모두 모델에 넣고

예측을 수행시킬 때는 답지(X_test)만 넣고 모델의 예측 결과를 답지(y_test)와 비교해서 평가를 수행한다

 

<모델 학습시킬 때>

# 학습 데이터의 문제(X_train)와 답지(y_train) 모두 삽입
모델.fit(X_train, y_train)

 

<모델 예측시킬 때>

# pred_value : 모델이 예측한 결과값
# 예측 데이터의 문제(X_test)만 삽입
pred_value = 학습시킨 모델.predict(X_test)

 

<모델 평가할 때>

# 답지(y_test)와 모델이 예측한 결과값(pred_value) 비교
mse = mean_squared_error(y_test, pred_value)