day 9 파이썬 데이터 전처리 기법
다양한 데이터 전처리 기법
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()
# IQR=Q3−Q1
# 즉, IQR은 제 3사분위수에서 제 1사분위 값을 뺀 값으로 데이터의 중간 50%의 범위라고 생각하시면 됩니다.
# Q_1 - 1.5*IQRQ1
# −1.5∗IQR보다 왼쪽에 있거나, Q_3 + 1.5*IQR
# Q3+1.5∗IQR 보다 오른쪽에 있는 경우 우리는 이상치라고 판단합니다.
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)
# 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()
# 다음 이미지는 동일한 데이터를 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()
# 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:>
# 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