day 9 파이썬 데이터 전처리 기법

15 minute read

다양한 데이터 전처리 기법

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
print("👽 Hello.")

👽 Hello.

1. intro

import os

csv_file_path = os.path.abspath(os.getcwd())+ '/aiffel/data_preprocess/data/trade.csv'
trade = pd.read_csv(csv_file_path) 
trade.head()
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지 기타사항
0 2015년 01월 중국 116932.0 12083947.0 334522.0 8143271.0 3940676.0 NaN
1 2015년 01월 미국 65888.0 5561545.0 509564.0 3625062.0 1936484.0 NaN
2 2015년 01월 일본 54017.0 2251307.0 82480.0 3827247.0 -1575940.0 NaN
3 2015년 02월 중국 86228.0 9927642.0 209100.0 6980874.0 2946768.0 NaN
4 2015년 02월 미국 60225.0 5021264.0 428678.0 2998216.0 2023048.0 NaN

2. 결측치(Missing Data)


# 결측치를 처리하는 방법은 크게 두 가지가 있습니다.

# 결측치가 있는 데이터를 제거한다.
# 결측치를 어떤 값으로 대체한다.
# 결측치를 대체하는 방법은 다양한데 데이터마다 특성을 반영하여 해결해야 합니다.
# 전체 데이터 건수에서 각 컬럼별 값이 있는 데이터 수를 빼주면 컬럼별 결측치의 개수를 알 수 있습니다.
print('전체 데이터 건수:', len(trade))
전체 데이터 건수: 199
print('컬럼별 결측치 개수')
len(trade) - trade.count()
컬럼별 결측치 개수





기간        0
국가명       0
수출건수      3
수출금액      4
수입건수      3
수입금액      3
무역수지      4
기타사항    199
dtype: int64
# '기타사항'을 보시면 전부 결측치라는 것을 알 수 있습니다. 이는 아무런 정보가 없는 컬럼이므로 삭제하도록 하겠습니다.
trade = trade.drop('기타사항', axis=1)
trade.head()
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
0 2015년 01월 중국 116932.0 12083947.0 334522.0 8143271.0 3940676.0
1 2015년 01월 미국 65888.0 5561545.0 509564.0 3625062.0 1936484.0
2 2015년 01월 일본 54017.0 2251307.0 82480.0 3827247.0 -1575940.0
3 2015년 02월 중국 86228.0 9927642.0 209100.0 6980874.0 2946768.0
4 2015년 02월 미국 60225.0 5021264.0 428678.0 2998216.0 2023048.0
# DataFrame.isnull()은 데이터마다 결측치 여부를 True, False로 반환합니다.
# DataFrame.any(axis=1)는 행마다 하나라도 True가 있으면 True, 그렇지 않으면 False를 반환합니다.
# '각 행이 결측치가 하나라도 있는지' 여부를 불리언 값으로 가진 Series가 출력됩니다.
trade.isnull()
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
0 False False False False False False False
1 False False False False False False False
2 False False False False False False False
3 False False False False False False False
4 False False False False False False False
... ... ... ... ... ... ... ...
194 False False False False False False False
195 False False False False False False False
196 False False True True True True True
197 False False True True True True True
198 False False True True True True True

199 rows × 7 columns

trade.isnull().any(axis=1)
0      False
1      False
2      False
3      False
4      False
       ...  
194    False
195    False
196     True
197     True
198     True
Length: 199, dtype: bool
# trade.isnull().any(axis=1)을 다시 DataFrame에 넣어주면 값이 True인 데이터만 추출해줍니다.
trade[trade.isnull().any(axis=1)]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
191 2020년 04월 미국 105360.0 NaN 1141118.0 5038739.0 NaN
196 2020년 06월 중국 NaN NaN NaN NaN NaN
197 2020년 06월 미국 NaN NaN NaN NaN NaN
198 2020년 06월 일본 NaN NaN NaN NaN NaN
# 우선 '수출건수', '수출금액', '수입건수', '수입금액', '무역수지' 열이 모두 결측치인 index 196, 197, 198을 삭제하겠습니다.
trade.dropna(how='all', subset=['수출건수', '수출금액', '수입건수', '수입금액', '무역수지'], inplace=True)
print("👽 It's okay, no biggie.")
👽 It's okay, no biggie.
trade[trade.isnull().any(axis=1)]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
191 2020년 04월 미국 105360.0 NaN 1141118.0 5038739.0 NaN
# 특정 값을 지정해줄 수 있습니다. 그러나 결측치가 많은 경우, 모두 같은 값으로 대체한다면 데이터의 분산이 실제보다 작아지는 문제가 생길 수 있습니다.

# 평균, 중앙값 등으로 대체할 수 있습니다. 1번에서 특정 값으로 대체했을 때와 마찬가지로 결측치가 많은 경우 데이터의 분산이 실제보다 작아지는 문제가 발생할 수 있습니다.

# 다른 데이터를 이용해 예측값으로 대체할 수 있습니다. 예를 들어 머신러닝 모델로 2020년 4월 미국의 예측값을 만들고, 이 값으로 결측치를 보완할 수 있습니다.

# 시계열 특성을 가진 데이터의 경우 앞뒤 데이터를 통해 결측치를 대체할 수 있습니다. 예를 들어 기온을 측정하는 센서 데이터에서 결측치가 발생할 경우, 전후 데이터의 평균으로 보완할 수 있습니다.
# index 191은 3번 방법을 통해 보완하도록 하겠습니다.
# 다른 데이터를 이용해 예측값으로 대체할 수 있다.
trade.loc[191,'무역수지']=trade['무역수지'].mean()
trade.loc[[191]]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
191 2020년 04월 미국 105360.0 5946782.0 1141118.0 5038739.0 1.000219e+06
# index 191은 4번 방법을 통해 보완하도록 하겠습니다.

# DataFrame.loc[행 라벨, 열 라벨]을 입력하면 해당 라벨을 가진 데이터를 출력해줍니다.
trade.loc[[188, 191, 194]]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
188 2020년 03월 미국 97117.0 7292838.0 1368345.0 5388338.0 1.904500e+06
191 2020년 04월 미국 105360.0 5946782.0 1141118.0 5038739.0 1.000219e+06
194 2020년 05월 미국 126598.0 4600726.0 1157163.0 4286873.0 3.138530e+05
trade.loc[191, '수출금액'] = (trade.loc[188, '수출금액'] + trade.loc[194, '수출금액'] )/2
trade.loc[[191]]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
191 2020년 04월 미국 105360.0 5946782.0 1141118.0 5038739.0 1.000219e+06
# index 191의 무역수지 컬럼은 수출금액과 수입금액의 차이를 이용하여 채우도록 하겠습니다.
trade.loc[191, '무역수지'] = trade.loc[191, '수출금액'] - trade.loc[191, '수입금액'] 
trade.loc[[191]]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
191 2020년 04월 미국 105360.0 5946782.0 1141118.0 5038739.0 908043.0

3. 중복된 데이터


# DataFrame.duplicated()는 중복된 데이터 여부를 불리언 값으로 반환해줍니다.
trade.duplicated()
0      False
1      False
2      False
3      False
4      False
       ...  
191    False
192    False
193    False
194    False
195    False
Length: 196, dtype: bool
trade[trade.duplicated()]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
187 2020년 03월 중국 248059.0 10658599.0 358234.0 8948918.0 1709682.0
trade[(trade['기간']=='2020년 03월')&(trade['국가명']=='중국')]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
186 2020년 03월 중국 248059.0 10658599.0 358234.0 8948918.0 1709682.0
187 2020년 03월 중국 248059.0 10658599.0 358234.0 8948918.0 1709682.0
# index 186, 187이 중복되어 있습니다.

# pandas에서는 DataFrame.drop_duplicates를 통해 중복된 데이터를 손쉽게 삭제할 수 있습니다.
trade.drop_duplicates(inplace=True)
print("👽 It's okay, no biggie.")
👽 It's okay, no biggie.
df = pd.DataFrame({'id':['001', '002', '003', '004', '002'], 
                   'name':['Park Yun', 'Kim Sung', 'Park Jin', 'Lee Han', 'Kim Min']})
df
id name
0 001 Park Yun
1 002 Kim Sung
2 003 Park Jin
3 004 Lee Han
4 002 Kim Min
# id가 002인 데이터가 2개 있습니다. id가 사람마다 unique 하다고 할 때, 둘 중 하나는 삭제해야 합니다.
# index가 클수록 나중에 들어온 데이터이고, 사용자가 이름을 수정했을 때 업데이트가 되지 않고 삽입이 되어 생긴 문제라고 가정합니다.
# 즉, id가 중복된 경우 맨 나중에 들어온 값만 남겨야 합니다.
# DataFrame.drop_duplicates의 subset, keep 옵션을 통해 손쉽게 중복을 제거할 수 있습니다.
df.drop_duplicates(subset=['id'], keep='last')
id name
0 001 Park Yun
2 003 Park Jin
3 004 Lee Han
4 002 Kim Min

4 이상치


# 가장 간단한 방법으로 이상치를 삭제할 수 있습니다. 이상치를 원래 데이터에서 삭제하고, 이상치끼리 따로 분석하는 방안도 있습니다.

# 이상치를 다른 값으로 대체할 수 있습니다. 데이터가 적으면 이상치를 삭제하기보다 다른 값으로 대체하는 것이 나을 수 있습니다. 예를 들어 최댓값, 최솟값을 설정해 데이터의 범위를 제한할 수 있습니다.

# 혹은 결측치와 마찬가지로 다른 데이터를 활용하여 예측 모델을 만들어 예측값을 활용할 수도 있습니다.

# 아니면 binning을 통해 수치형 데이터를 범주형으로 바꿀 수도 있습니다.
# z-score method

# abs(df[col] - np.mean(df[col])) : 데이터에서 평균을 빼준 것에 절대값을 취합니다.
    
# abs(df[col] - np.mean(df[col]))/np.std(df[col]) : 위에 한 작업에 표준편차로 나눠줍니다.
    
# df[abs(df[col] - np.mean(df[col]))/np.std(df[col])>z].index: 값이 z보다 큰 데이터의 인덱스를 추출합니다.
def outlier(df, col, z):
    return df[abs(df[col] - np.mean(df[col]))/np.std(df[col])>z].index
print("👽 It's okay, no biggie.")
👽 It's okay, no biggie.
trade.loc[outlier(trade, '무역수지', 1.5)]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
6 2015년 03월 중국 117529.0 11868032.0 234321.0 7226911.0 4641121.0
75 2017년 02월 중국 159062.0 11118131.0 188555.0 6600637.0 4517495.0
80 2017년 03월 일본 65093.0 2395932.0 165734.0 5157589.0 -2761657.0
96 2017년 09월 중국 183442.0 13540683.0 295443.0 8443414.0 5097269.0
99 2017년 10월 중국 137873.0 12580474.0 244977.0 7932403.0 4648071.0
101 2017년 10월 일본 63510.0 1847999.0 127696.0 4418583.0 -2570584.0
102 2017년 11월 중국 421194.0 14000887.0 307790.0 9253318.0 4747569.0
105 2017년 12월 중국 218114.0 13848364.0 290347.0 8600132.0 5248232.0
114 2018년 03월 중국 232396.0 13576667.0 267249.0 8412516.0 5164151.0
116 2018년 03월 일본 80142.0 2603450.0 159601.0 5226141.0 -2622691.0
120 2018년 05월 중국 214145.0 13851900.0 307183.0 9279720.0 4572180.0
123 2018년 06월 중국 257130.0 13814241.0 279023.0 8713018.0 5101223.0
126 2018년 07월 중국 181772.0 13721233.0 293164.0 8869278.0 4851955.0
129 2018년 08월 중국 199010.0 14401521.0 280033.0 8525532.0 5875989.0
132 2018년 09월 중국 171328.0 14590529.0 280337.0 7889890.0 6700639.0
135 2018년 10월 중국 169809.0 14767041.0 319876.0 9963108.0 4803932.0
trade.loc[outlier(trade, '무역수지', 2)]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
129 2018년 08월 중국 199010.0 14401521.0 280033.0 8525532.0 5875989.0
132 2018년 09월 중국 171328.0 14590529.0 280337.0 7889890.0 6700639.0
trade.loc[outlier(trade, '무역수지', 3)]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
# 무역수지의 이상치를 확인하는데 기준 되는 값이 클수록 이상치가 적어지는 것을 확인할 수 있습니다.

# 이제 not_outlier라는 함수를 통해 무역수지가 이상치 값이 아닌 데이터만 추출하도록 하겠습니다.

def not_outlier(df, col, z):
    return df[abs(df[col] - np.mean(df[col]))/np.std(df[col]) <= z].index
print("👽 It's okay, no biggie.")
👽 It's okay, no biggie.
trade.loc[not_outlier(trade, '무역수지', 1.5)]
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
0 2015년 01월 중국 116932.0 12083947.0 334522.0 8143271.0 3940676.0
1 2015년 01월 미국 65888.0 5561545.0 509564.0 3625062.0 1936484.0
2 2015년 01월 일본 54017.0 2251307.0 82480.0 3827247.0 -1575940.0
3 2015년 02월 중국 86228.0 9927642.0 209100.0 6980874.0 2946768.0
4 2015년 02월 미국 60225.0 5021264.0 428678.0 2998216.0 2023048.0
... ... ... ... ... ... ... ...
191 2020년 04월 미국 105360.0 5946782.0 1141118.0 5038739.0 908043.0
192 2020년 04월 일본 134118.0 1989323.0 141207.0 3989562.0 -2000239.0
193 2020년 05월 중국 185320.0 10746069.0 349007.0 8989920.0 1756149.0
194 2020년 05월 미국 126598.0 4600726.0 1157163.0 4286873.0 313853.0
195 2020년 05월 일본 166568.0 1798128.0 133763.0 3102734.0 -1304606.0

179 rows × 7 columns

# z-score 방법의 대안으로 사분위범위수 IQR(Interquartile range) 로 이상치를 알아내는 방법을 알아보겠습니다.

np.random.seed(2020)
data = np.random.randn(100)  # 평균 0, 표준편차 1의 분포에서 100개의 숫자를 샘플링한 데이터 생성
data = np.concatenate((data, np.array([8, 10, -3, -5])))      # [8, 10, -3, -5])를 데이터 뒤에 추가함
data
array([-1.76884571,  0.07555227, -1.1306297 , -0.65143017, -0.89311563,
       -1.27410098, -0.06115443,  0.06451384,  0.41011295, -0.57288249,
       -0.80133362,  1.31203519,  1.27469887, -1.2143576 ,  0.31371941,
       -1.44482142, -0.3689613 , -0.76922658,  0.3926161 ,  0.05729383,
        2.08997884,  0.04197131, -0.04834072, -0.51315392, -0.08458928,
       -1.21545008, -1.41293073, -1.48691055,  0.38222486,  0.937673  ,
        1.77267804,  0.87882801,  0.33171912, -0.30603567,  1.24026615,
       -0.21562684,  0.15592948,  0.09805553,  0.83209585,  2.04520542,a
       -0.31681392, -1.31283291, -1.75445746,  0.10209408, -1.36150208,
        0.48178488, -0.20832874, -0.09186351,  0.70268816,  0.10365506,
        0.62123638,  0.95411497,  2.03781352, -0.48445122,  0.2071549 ,
        1.64424216, -0.4882074 , -0.01782826,  0.46891556,  0.27987266,
       -0.64664972, -0.54406002, -0.16008985,  0.03781172,  1.03383296,
       -1.23096117, -1.24673665,  0.29572055,  2.1409624 , -0.92020227,
       -0.06000238,  0.27978391, -1.53126966, -0.30293101, -0.14601413,
        0.27746159, -0.13952066,  0.69515966, -0.11338746, -1.233267  ,
       -0.79614131, -0.46739138,  0.65890607, -0.41063115,  0.17344356,
        0.28946174,  1.03451736,  1.22661712,  1.71998252,  0.40806834,
        0.32256894,  1.04722748, -1.8196003 , -0.42582157,  0.12454883,
        2.31256634, -0.96557586, -0.34627486,  0.96668378, -0.92550192,
        8.        , 10.        , -3.        , -5.        ])
fig, ax = plt.subplots()
ax.boxplot(data)
plt.show()

output_37_0

# IQR=Q3−Q1

# 즉, IQR은 제 3사분위수에서 제 1사분위 값을 뺀 값으로 데이터의 중간 50%의 범위라고 생각하시면 됩니다.
# Q_1 - 1.5*IQRQ1

#  −1.5∗IQR보다 왼쪽에 있거나, Q_3 + 1.5*IQR

# Q3+1.5∗IQR 보다 오른쪽에 있는 경우 우리는 이상치라고 판단합니다.

image

Q3, Q1 = np.percentile(data, [75 ,25])
IQR = Q3 - Q1
IQR
1.1644925829790964
data[(Q1-1.5*IQR > data)|(Q3+1.5*IQR < data)]
array([ 2.31256634,  8.        , 10.        , -3.        , -5.        ])
# 무역수지를 기준으로 이상치를 찾는 실습을 해보도록 하겠습니다
def outlier2(df, col):
    q1 = df[col].quantile(0.25)
    q3 = df[col].quantile(0.75)
    iqr = q3 - q1
    return df[(df[col] < q1-1.5*iqr)|(df[col] > q3+1.5*iqr)]

outlier2(trade, '무역수지')
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지

5. 정규화(Normalization)

image

# trade 데이터를 보면 수입건수, 수출건수와 수입금액, 수출금액, 무역수지는 단위가 다르다는 것을 알 수 있습니다.

# 이처럼 컬럼마다 스케일이 크게 차이가 나는 데이터를 입력하면 머신러닝 모델 학습에 문제가 발생할 수 있습니다.

# 모델의 파라미터를 업데이트하는 과정에서 범위가 큰 컬럼 B의 파라메터만 집중적으로 업데이트하는 문제가 생길 수 있습니다.

# 그래서 일반적으로 컬럼간에 범위가 크게 다를 경우 전처리 과정에서 데이터를 정규화합니다.

# 정규화를 하는 방법은 다양하지만, 가장 잘 알려진 표준화(Standardization)와 Min-Max Scaling을 알아보도록 하겠습니다.
# 정규분포를 따라 랜덤하게 데이터 x를 생성합니다. 
np.random.seed(2020)
x = pd.DataFrame({'A': np.random.randn(100)*4+4,
                 'B': np.random.randn(100)-1})
x
A B
0 -3.075383 -0.448552
1 4.302209 -2.151012
2 -0.522519 -1.245304
3 1.394279 -1.169169
4 0.427537 -0.458080
... ... ...
95 13.250265 -0.734271
96 0.137697 -2.041710
97 2.614901 -3.057545
98 7.866735 -2.140529
99 0.297992 -0.503868

100 rows × 2 columns

# 데이터 x를 Standardization 기법으로 정규화합니다. 
x_standardization = (x - x.mean())/x.std()
x_standardization
A B
0 -1.862058 0.559307
1 0.072383 -1.132240
2 -1.192684 -0.232338
3 -0.690090 -0.156691
4 -0.943575 0.549839
... ... ...
95 2.418607 0.275419
96 -1.019573 -1.023639
97 -0.370038 -2.032961
98 1.007019 -1.121824
99 -0.977542 0.504345

100 rows × 2 columns

# 데이터 x를 min-max scaling 기법으로 정규화합니다. 
x_min_max = (x-x.min())/(x.max()-x.min())
x_min_max
A B
0 0.012283 0.607216
1 0.458634 0.267692
2 0.166733 0.448318
3 0.282702 0.463502
4 0.224213 0.605316
... ... ...
95 1.000000 0.550235
96 0.206677 0.289490
97 0.356550 0.086901
98 0.674291 0.269782
99 0.216375 0.596184

100 rows × 2 columns

# 다음 이미지는 데이터를 Standardization 기법으로 정규화를 했을 때 분포가 어떻게 바뀌는지 보여줍니다. 즉, 각 컬럼의 평균은 0으로, 분산은 1로 데이터를 바꿔줍니다
fig, axs = plt.subplots(1,2, figsize=(12, 4),
                        gridspec_kw={'width_ratios': [2, 1]})

axs[0].scatter(x['A'], x['B'])
axs[0].set_xlim(-5, 15)
axs[0].set_ylim(-5, 5)
axs[0].axvline(c='grey', lw=1)
axs[0].axhline(c='grey', lw=1)
axs[0].set_title('Original Data')

axs[1].scatter(x_standardization['A'], x_standardization['B'])
axs[1].set_xlim(-5, 5)
axs[1].set_ylim(-5, 5)
axs[1].axvline(c='grey', lw=1)
axs[1].axhline(c='grey', lw=1)
axs[1].set_title('Data after standardization')

plt.show()

output_48_0

# 다음 이미지는 동일한 데이터를 min-max scaling 기법으로 정규화를 했을 때 분포가 어떻게 바뀌는지 보여줍니다. 
# 즉, 각 컬럼의 최솟값은 0, 최댓값은 1로 바꿔줍니다.

fig, axs = plt.subplots(1,2, figsize=(12, 4),
                        gridspec_kw={'width_ratios': [2, 1]})

axs[0].scatter(x['A'], x['B'])
axs[0].set_xlim(-5, 15)
axs[0].set_ylim(-5, 5)
axs[0].axvline(c='grey', lw=1)
axs[0].axhline(c='grey', lw=1)
axs[0].set_title('Original Data')

axs[1].scatter(x_min_max['A'], x_min_max['B'])
axs[1].set_xlim(-5, 5)
axs[1].set_ylim(-5, 5)
axs[1].axvline(c='grey', lw=1)
axs[1].axhline(c='grey', lw=1)
axs[1].set_title('Data after min-max scaling')

plt.show()

output_49_0

# Standardization

# 우선 정규화를 시켜야 할 수치형 컬럼들을 cols 변수에 담은 후, 데이터에서 평균을 빼고, 표준편차로 나눠주도록 합니다.
# trade 데이터를 Standardization 기법으로 정규화합니다. 
cols = ['수출건수', '수출금액', '수입건수', '수입금액', '무역수지']
trade_Standardization= (trade[cols]-trade[cols].mean())/trade[cols].std()
trade_Standardization.head()
수출건수 수출금액 수입건수 수입금액 무역수지
0 -0.007488 1.398931 -0.163593 1.283660 1.256342
1 -0.689278 -0.252848 0.412529 -0.964444 0.401088
2 -0.847838 -1.091156 -0.993148 -0.863844 -1.097779
3 -0.417598 0.852853 -0.576399 0.705292 0.832209
4 -0.764918 -0.389673 0.146306 -1.276341 0.438027
trade_Standardization.describe()
수출건수 수출금액 수입건수 수입금액 무역수지
count 1.950000e+02 1.950000e+02 1.950000e+02 1.950000e+02 1.950000e+02
mean -1.019128e-16 5.921189e-17 6.091993e-17 -8.312439e-17 3.074464e-17
std 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00
min -9.194976e-01 -1.231761e+00 -9.984408e-01 -1.276341e+00 -1.603764e+00
25% -5.937426e-01 -1.041338e+00 -7.673625e-01 -7.911669e-01 -1.116765e+00
50% -4.373265e-01 -1.564700e-01 -3.429346e-01 -4.137392e-01 1.426824e-01
75% 4.420459e-01 1.037200e+00 3.927781e-01 8.827841e-01 7.461637e-01
max 5.486317e+00 2.078416e+00 3.239068e+00 2.376092e+00 2.434109e+00
# Min-Max Scaling
# 데이터에서 최솟값을 빼주고, '최댓값-최솟값'으로 나눠줍니다.
# trade 데이터를 min-max scaling 기법으로 정규화합니다. 
trade[cols] = (trade[cols]-trade[cols].min())/(trade[cols].max()-trade[cols].min())
trade.head()
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지
0 2015년 01월 중국 0.142372 0.794728 0.197014 0.700903 0.708320
1 2015년 01월 미국 0.035939 0.295728 0.332972 0.085394 0.496512
2 2015년 01월 일본 0.011187 0.042477 0.001249 0.112938 0.125310
3 2015년 02월 중국 0.078351 0.629759 0.099597 0.542551 0.603281
4 2015년 02월 미국 0.024131 0.254394 0.270146 0.000000 0.505660
# Min-Max Scaling 방법으로 정규화시킨 후, 각 컬럼의 최솟값(min)은 0이고, 최댓값(max)은 1임을 확인할 수 있습니다.
trade.describe()
수출건수 수출금액 수입건수 수입금액 무역수지
count 195.000000 195.000000 195.000000 195.000000 195.000000
mean 0.143541 0.372113 0.235620 0.349450 0.397180
std 0.156108 0.302099 0.235988 0.273790 0.247655
min 0.000000 0.000000 0.000000 0.000000 0.000000
25% 0.050853 0.057527 0.054532 0.132836 0.120608
50% 0.075271 0.324844 0.154691 0.236172 0.432516
75% 0.212548 0.685450 0.328311 0.591147 0.581972
max 1.000000 1.000000 1.000000 1.000000 1.000000
# train 데이터와 test 데이터가 나눠져 있는 경우 train 데이터를 정규화시켰던 기준 그대로 test 데이터도 정규화 
train = pd.DataFrame([[10, -10], [30, 10], [50, 0]])
test = pd.DataFrame([[0, 1], [10, 10]])
print("👽 It's okay, no biggie.")

👽 It's okay, no biggie.
train_min = train.min()
train_max = train.max()

train_min_max = (train - train_min)/(train_max - train_min)
test_min_max =  (test - train_min)/(train_max - train_min)    # test를 min-max scaling할 때도 train 정규화 기준으로 수행
print("💫 It's okay, no biggie...")
💫 It's okay, no biggie...
train_min_max
0 1
0 0.0 0.0
1 0.5 1.0
2 1.0 0.5
test_min_max
0 1
0 -0.25 0.55
1 0.00 1.00
from sklearn.preprocessing import MinMaxScaler
train = [[10, -10], [30, 10], [50, 0]]
test = [[0, 1]]
scaler = MinMaxScaler()
print("👽 It's okay, no biggie.")
👽 It's okay, no biggie.
scaler.fit_transform(train)
array([[0. , 0. ],
       [0.5, 1. ],
       [1. , 0.5]])
scaler.transform(test)
array([[-0.25,  0.55]])

6. 원-핫 인코딩(One-Hot Encoding)


# 원-핫 인코딩이란 카테고리별 이진 특성을 만들어 해당하는 특성만 1, 나머지는 0으로 만드는 방법입니다. 
# 그럼, pandas로 국가명 컬럼을 원-핫 인코딩을 해보겠습니다.
#trade 데이터의 국가명 컬럼 원본
print(trade['국가명'].head())  

# get_dummies를 통해 국가명 원-핫 인코딩
country = pd.get_dummies(trade['국가명'])
country.head()
0    중국
1    미국
2    일본
3    중국
4    미국
Name: 국가명, dtype: object
미국 일본 중국
0 0 0 1
1 1 0 0
2 0 1 0
3 0 0 1
4 1 0 0
trade = pd.concat([trade, country], axis=1)
trade.head()
기간 국가명 수출건수 수출금액 수입건수 수입금액 무역수지 미국 일본 중국
0 2015년 01월 중국 0.142372 0.794728 0.197014 0.700903 0.708320 0 0 1
1 2015년 01월 미국 0.035939 0.295728 0.332972 0.085394 0.496512 1 0 0
2 2015년 01월 일본 0.011187 0.042477 0.001249 0.112938 0.125310 0 1 0
3 2015년 02월 중국 0.078351 0.629759 0.099597 0.542551 0.603281 0 0 1
4 2015년 02월 미국 0.024131 0.254394 0.270146 0.000000 0.505660 1 0 0
trade.drop(['국가명'], axis=1, inplace=True)
trade.head()
기간 수출건수 수출금액 수입건수 수입금액 무역수지 미국 일본 중국
0 2015년 01월 0.142372 0.794728 0.197014 0.700903 0.708320 0 0 1
1 2015년 01월 0.035939 0.295728 0.332972 0.085394 0.496512 1 0 0
2 2015년 01월 0.011187 0.042477 0.001249 0.112938 0.125310 0 1 0
3 2015년 02월 0.078351 0.629759 0.099597 0.542551 0.603281 0 0 1
4 2015년 02월 0.024131 0.254394 0.270146 0.000000 0.505660 1 0 0

7. 구간화(Binning)



# 다른 전처리 기법을 배워보도록 하겠습니다.

# salary에 소득 데이터가 있다고 합시다.
salary = pd.Series([4300, 8370, 1750, 3830, 1840, 4220, 3020, 2290, 4740, 4600, 
                    2860, 3400, 4800, 4470, 2440, 4530, 4850, 4850, 4760, 4500, 
                    4640, 3000, 1880, 4880, 2240, 4750, 2750, 2810, 3100, 4290, 
                    1540, 2870, 1780, 4670, 4150, 2010, 3580, 1610, 2930, 4300, 
                    2740, 1680, 3490, 4350, 1680, 6420, 8740, 8980, 9080, 3990, 
                    4960, 3700, 9600, 9330, 5600, 4100, 1770, 8280, 3120, 1950, 
                    4210, 2020, 3820, 3170, 6330, 2570, 6940, 8610, 5060, 6370,
                    9080, 3760, 8060, 2500, 4660, 1770, 9220, 3380, 2490, 3450, 
                    1960, 7210, 5810, 9450, 8910, 3470, 7350, 8410, 7520, 9610, 
                    5150, 2630, 5610, 2750, 7050, 3350, 9450, 7140, 4170, 3090])
print("👽 Almost there..")
👽 Almost there..
# 이 데이터를 구간별로 나누고자 합니다. 이러한 기법을 구간화(Data binning 혹은 bucketing)이라고 부릅니다.

# 아래 히스토그램과 같이 연속적인 데이터를 구간을 나눠 분석할 때 사용하는 방법입니다.
salary.hist()
<AxesSubplot:>

output_68_1

# pandas의 cut 과 qcut을 이용해 수치형 데이터를 범주형 데이터로 변형시키도록 하겠습니다.
bins = [0, 2000, 4000, 6000, 8000, 10000]
print("👽 Almost there..")
👽 Almost there..
ctg = pd.cut(salary, bins=bins)
ctg
0      (4000, 6000]
1     (8000, 10000]
2         (0, 2000]
3      (2000, 4000]
4         (0, 2000]
          ...      
95     (2000, 4000]
96    (8000, 10000]
97     (6000, 8000]
98     (4000, 6000]
99     (2000, 4000]
Length: 100, dtype: category
Categories (5, interval[int64]): [(0, 2000] < (2000, 4000] < (4000, 6000] < (6000, 8000] < (8000, 10000]]
# alary[0]는 4300으로 4000에서 6000 사이에 포함되었다는 것을 확인할 수 있습니다.
print('salary[0]:', salary[0])
print('salary[0]가 속한 카테고리:', ctg[0])
salary[0]: 4300
salary[0]가 속한 카테고리: (4000, 6000]
# 구간별로 값이 몇 개가 속해 있는지 value_counts()로 확인해보겠습니다.


ctg.value_counts().sort_index()
(0, 2000]        12
(2000, 4000]     34
(4000, 6000]     29
(6000, 8000]      9
(8000, 10000]    16
dtype: int64
# 이렇게 특정 구간을 지정해줘도 되고, 구간의 개수를 지정해줄 수도 있습니다. 
# bins 옵션에 정수를 입력하면 데이터의 최솟값에서 최댓값을 균등하게 bins 개수만큼 나눠줍니다.
ctg = pd.cut(salary, bins=6)
ctg
0      (4230.0, 5575.0]
1      (8265.0, 9610.0]
2     (1531.93, 2885.0]
3      (2885.0, 4230.0]
4     (1531.93, 2885.0]
            ...        
95     (2885.0, 4230.0]
96     (8265.0, 9610.0]
97     (6920.0, 8265.0]
98     (2885.0, 4230.0]
99     (2885.0, 4230.0]
Length: 100, dtype: category
Categories (6, interval[float64]): [(1531.93, 2885.0] < (2885.0, 4230.0] < (4230.0, 5575.0] < (5575.0, 6920.0] < (6920.0, 8265.0] < (8265.0, 9610.0]]
ctg.value_counts().sort_index()
(1531.93, 2885.0]    27
(2885.0, 4230.0]     24
(4230.0, 5575.0]     21
(5575.0, 6920.0]      6
(6920.0, 8265.0]      7
(8265.0, 9610.0]     15
dtype: int64
# qcut은 구간을 일정하게 나누는 것이 아니라 데이터의 분포를 비슷한 크기의 그룹으로 나눠줍니다.
ctg = pd.qcut(salary, q=5)
ctg
0       (3544.0, 4648.0]
1       (7068.0, 9610.0]
2     (1539.999, 2618.0]
3       (3544.0, 4648.0]
4     (1539.999, 2618.0]
             ...        
95      (2618.0, 3544.0]
96      (7068.0, 9610.0]
97      (7068.0, 9610.0]
98      (3544.0, 4648.0]
99      (2618.0, 3544.0]
Length: 100, dtype: category
Categories (5, interval[float64]): [(1539.999, 2618.0] < (2618.0, 3544.0] < (3544.0, 4648.0] < (4648.0, 7068.0] < (7068.0, 9610.0]]
print(ctg.value_counts().sort_index())
print(".\n.\n🛸 Well done!")
(1539.999, 2618.0]    20
(2618.0, 3544.0]      20
(3544.0, 4648.0]      20
(4648.0, 7068.0]      20
(7068.0, 9610.0]      20
dtype: int64
.
.
🛸 Well done!


Leave a comment