IT/언어

[python] 머신러닝을 위한 Python(8) ; Data Cleansing

개발자 두더지 2020. 6. 5. 19:51
728x90

1) Data quality problems

- 데이터의 최대/최소가 다름 -> Scale에 따른 y값에 영향

- Ordinary 또는 Nominal한 값들(범주형 데이터)의 표현은 어떻게?

- 잘못 기입된 값들에 대한 처리

- 값이 없을 경우는 어떻게?

- 극단적으로 큰 값 또는 작은 값들은 그대로 놔둬야 하는가?


2) Data preprocessing issues

기본적으로 아래의 세 가지에 대해 전처리를 한다.

- 데이터가 빠진 경우 (결측치의 처리)

- 라벨링된 데이터(category) 데이터의 처리

- 데이터의 scale의 차이가 매우 크게 날 경우


3) Missing Values

데이터가 없을 때 할 수 있는 전략은 아래와 같다.

◆ 데이터가 없으면 sample을 drop

◆ 데이터가 없는 최소 개수를 정해서 sample을 drop

◆ 데이터가 거의 없는 feature는 feature 자체를 drop

◆ 최빈값, 평균값으로 비어있는 데이터를 채우기

여기서 사용할 데이터는 아래와 같다.

import pandas as pd
import numpy as np

# Eaxmple from - https://chrisalbon.com/python/pandas_missing_data.html
raw_data = {'first_name': ['Jason', np.nan, 'Tina', 'Jake', 'Amy'],
        'last_name': ['Miller', np.nan, 'Ali', 'Milner', 'Cooze'],
        'age': [42, np.nan, 36, 24, 73],
        'sex': ['m', np.nan, 'f', 'm', 'f'],
        'preTestScore': [4, np.nan, np.nan, 2, 3],
        'postTestScore': [25, np.nan, np.nan, 62, 70]}
df = pd.DataFrame(raw_data, columns = ['first_name', 'last_name', 'age', 'sex', 'preTestScore', 'postTestScore'])

print(df)

>>    first_name last_name   age  sex  preTestScore  postTestScore
0      Jason    Miller  42.0    m           4.0           25.0
1        NaN       NaN   NaN  NaN           NaN            NaN
2       Tina       Ali  36.0    f           NaN            NaN
3       Jake    Milner  24.0    m           2.0           62.0
4        Amy     Cooze  73.0    f           3.0           70.0

 

(1) Data drop

# column별로 NaN이 데이터의 수 합계를 표시
df.isnull().sum
>> 
first_name       1
last_name        1
age              1
sex              1
preTestScore     2
postTestScore    2
dtype: int64
# 사실상 개수보다는 전체 데이터의 수에 대한 NaN값의 비율이 더 중요하며 df.isnull().sum / len(df) 코드로 확인가능하다.



# dropna() -> NaN 포함된 데이터들이 사라짐
df_no_missing = df.dropna()
print(df_no_missing)
>>
  first_name last_name   age sex  preTestScore  postTestScore
0      Jason    Miller  42.0   m           4.0           25.0
3       Jake    Milner  24.0   m           2.0           62.0
4        Amy     Cooze  73.0   f           3.0           70.0



#  모든 데이터가 비어 있는 행 drop
df_cleaned = df.dropna(how='all')
print(df_cleaned)
>>
  first_name last_name   age sex  preTestScore  postTestScore
0      Jason    Miller  42.0   m           4.0           25.0
2       Tina       Ali  36.0   f           NaN            NaN
3       Jake    Milner  24.0   m           2.0           62.0
4        Amy     Cooze  73.0   f           3.0           70.0



# 모든 데이터가 NaN인 column 생성
df['location'] = np.nan
print(df)
>> 
  first_name last_name   age  sex  preTestScore  postTestScore  location
0      Jason    Miller  42.0    m           4.0           25.0       NaN
1        NaN       NaN   NaN  NaN           NaN            NaN       NaN
2       Tina       Ali  36.0    f           NaN            NaN       NaN
3       Jake    Milner  24.0    m           2.0           62.0       NaN
4        Amy     Cooze  73.0    f           3.0           70.0       NaN



# 축(여기서는 column)을 기준으로 데이터가 최소 4개 이상 없는 column을 drop, axis를 지정하지 않으면 행 기준으로 drop
result = df.dropna(axis=1, thresh=3)
print(result)
>>
  first_name last_name   age  sex  preTestScore  postTestScore
0      Jason    Miller  42.0    m           4.0           25.0
1        NaN       NaN   NaN  NaN           NaN            NaN
2       Tina       Ali  36.0    f           NaN            NaN
3       Jake    Milner  24.0    m           2.0           62.0
4        Amy     Cooze  73.0    f           3.0           70.0



# column 기준으로 삭제
result = df.dropna(axis=1, how='all')
print(result)
>> 
  first_name last_name   age  sex  preTestScore  postTestScore
0      Jason    Miller  42.0    m           4.0           25.0
1        NaN       NaN   NaN  NaN           NaN            NaN
2       Tina       Ali  36.0    f           NaN            NaN
3       Jake    Milner  24.0    m           2.0           62.0
4        Amy     Cooze  73.0    f           3.0           70.0

 

(2) Data Fill

평균값, 중위값, 최빈값을 활용

◆ 평균값 : 해당 column의 값의 평균을 내서 채우기

df["preTestScore"].mean()

◆ 중위값 : 값을 일렬로 나열 했을 때 중간에 위치한 값

df["preTestScore"].median()

◆ 최빈값 : 가장 많이 나오는 값

df["preTestScore"].mode()

# 데이터가 없는 곳에 0 대입
df.fillna(0)
>>   first_name last_name   age sex  preTestScore  postTestScore  location
0      Jason    Miller  42.0   m           4.0           25.0       0.0
1          0         0   0.0   0           0.0            0.0       0.0
2       Tina       Ali  36.0   f           0.0            0.0       0.0
3       Jake    Milner  24.0   m           2.0           62.0       0.0
4        Amy     Cooze  73.0   f           3.0           70.0       0.0



# 데이터가 없는 곳에 preTestScore의 평균값 삽입
df["preTestScore"].fillna(df["preTestScore"].mean(), inplace=True)
print(df)
>>  first_name last_name   age  sex  preTestScore  postTestScore  location
0      Jason    Miller  42.0    m           4.0           25.0       NaN
1        NaN       NaN   NaN  NaN           3.0            NaN       NaN
2       Tina       Ali  36.0    f           3.0            NaN       NaN
3       Jake    Milner  24.0    m           2.0           62.0       NaN
4        Amy     Cooze  73.0    f           3.0           70.0       NaN



# 성별로 나눠서 평균 값 삽입
df["preTestScore"].fillna(df.groupby("sex")["preTestScore"].transform("mean"), inplace=True)
print(df)
>>  first_name last_name   age  sex  preTestScore  postTestScore  location
0      Jason    Miller  42.0    m           4.0           25.0       NaN
1        NaN       NaN   NaN  NaN           NaN            NaN       NaN
2       Tina       Ali  36.0    f           3.0            NaN       NaN
3       Jake    Milner  24.0    m           2.0           62.0       NaN
4        Amy     Cooze  73.0    f           3.0           70.0       NaN



# Age와 sex가 모두 notnull인 경우에만 표시
print(df[df['age'].notnull() & df['sex'].notnull()])
>>  first_name last_name   age sex  preTestScore  postTestScore  location
0      Jason    Miller  42.0   m           4.0           25.0       NaN
2       Tina       Ali  36.0   f           NaN            NaN       NaN
3       Jake    Milner  24.0   m           2.0           62.0       NaN
4        Amy     Cooze  73.0   f           3.0           70.0       NaN

4) Category data

- 이산형 데이터(범주형 데이터)를 어떻게 처리할까? => One-Hot Encoding

{green, blue, yellow} 데이터 집합

인덱스 부여

{Green} -> [ 1, 0, 0 ]

{Green} -> [ 1, 0, 0 ]

{blue} -> [0, 1, 0]

실제 데이터 set의 크기만큼 Binary Feature를 생성

 

(1) Data type

import pandas as pd
import numpy as np

edges = pd.DataFrame({'source': [0, 1, 2],
                   'target': [2, 2, 3],
                       'weight': [3, 4, 5],
                       'color': ['red', 'blue', 'blue']})

print(edges)
>>    source  target  weight color
0       0       2       3   red
1       1       2       4  blue
2       2       3       5  blue



# source의 Data의 type = int64
print(["source"])
>> 0    0
   1    1
   2    2
Name: source, dtype: int64



# color의 Data의 type = obejct
print(edges["color"])
​

 

(2) One Hot Encoding

# pandas의 get_dummies()메소드는 obejct를 one hot ecoding시키는 메소드이다. 
# get_dummies()메소드가 알아서 object인 color를 기준으로 one hot ecoding시킨 결과이다.
result = pd.get_dummies(edges)
print(result)
>>    source  target  weight  color_blue  color_red
0       0       2       3           0          1
1       1       2       4           1          0
2       2       3       5           1          0



# edges에서 color만 끊어서도 one hot encoding할 수 있다.
# 데이터가 너무 많은 경우 끊어서 볼 때 유용하게 사용할 수 있다.
result = pd.get_dummies(edges["color"])
print(result)
>>  blue  red
0     0    1
1     1    0
2     1    0



result = pd.get_dummies(edges[["color"]])
print(result)
>>    color_blue  color_red
0           0          1
1           1          0
2           1          0



# Ordinary data -> One Hot Encoding
# 구분을 위해 임의로 사이즈 별로 3, 4, 5라는 데이터를 넣었지만 
# 컴퓨터가 그 자체를 숫자 값으로 인식하면 안 되기 때문에 category데이터로 변경해줘야한다.
# 그러기 위해서는 dict타입으로 만들어주고, map을 시켜주면 값에 맞게 M, L, XL 컬럼이 추가 된다. 
weight_dict = {3:"M", 4:"L", 5:"XL"}
edges["weight_sign"] = edges["weight"].map(weight_dict)
print(edges)
>>    source  target  weight color weight_sign
0       0       2       3   red           M
1       1       2       4  blue           L
2       2       3       5  blue          XL


# 위에서 수정한 데이터를 다시 .get_dummies()를 이용하여 One Hot Encoding시키면 feature을 뽑을 수 있다.
# 그러나 One Hot Encoding은 굉장히 많은 0 데이터를 만들기 때문에 차원의 저주에 걸릴 수 있으므로 best 방식은 아니다.
# 그러므로 One Hot Encoding후 여러 조치를 취하지만 여기서는 다루지 않도록 하겠다.
# 참고로 강의 자료에 있는 edges.as_matrix()는 더 이상 사용불가
edges = pd.get_dummies(edges)
result = edges.to_numpy() # 혹은 pd.get_dummies(edges).values
print(result)
>> [[0 2 3 0 1 0 1 0]
    [1 2 4 1 0 1 0 0]
    [2 3 5 1 0 0 0 1]]
as_matrix() 메소드의 목적은 frame을 Numpy-array로 표현하는 것이지만 as_matrix() 메소드는 버전 0.23.0이후로 사라졌으니,
0.23.0이후의 버전은 DataFrame.values() 를 대신 사용하도록 권장되고 있다.
즉, frame을 Numpy-array로 표현하기 위한 두 가지 방법이 있다.

(1) .values() : numpy.ndarray를 반환한다.
(2) .to_numpy() : numpy.ndarray를 반환한다.

 

(3) Data Binning

그래프 상에 퍼져있는 데이터에 대해 구간을 나눠 줄 필요가 있다. 사용할 데이터는 아래와 같다.

import pandas as pd
import numpy as np

raw_data = {'regiment': ['Nighthawks', 'Nighthawks', 'Nighthawks', 'Nighthawks', 'Dragoons', 'Dragoons', 'Dragoons', 'Dragoons', 'Scouts', 'Scouts', 'Scouts', 'Scouts'],
        'company': ['1st', '1st', '2nd', '2nd', '1st', '1st', '2nd', '2nd','1st', '1st', '2nd', '2nd'],
        'name': ['Miller', 'Jacobson', 'Ali', 'Milner', 'Cooze', 'Jacon', 'Ryaner', 'Sone', 'Sloan', 'Piger', 'Riani', 'Ali'],
        'preTestScore': [4, 24, 31, 2, 3, 4, 24, 31, 2, 3, 2, 3],
        'postTestScore': [25, 94, 57, 62, 70, 25, 94, 57, 62, 70, 62, 70]}
df = pd.DataFrame(raw_data, columns = ['regiment', 'company', 'name', 'preTestScore', 'postTestScore'])

print(df)

>>    regiment company      name  preTestScore  postTestScore
0   Nighthawks     1st    Miller             4             25
1   Nighthawks     1st  Jacobson            24             94
2   Nighthawks     2nd       Ali            31             57
3   Nighthawks     2nd    Milner             2             62
4     Dragoons     1st     Cooze             3             70
5     Dragoons     1st     Jacon             4             25
6     Dragoons     2nd    Ryaner            24             94
7     Dragoons     2nd      Sone            31             57
8       Scouts     1st     Sloan             2             62
9       Scouts     1st     Piger             3             70
10      Scouts     2nd     Riani             2             62
11      Scouts     2nd       Ali          
# Define bins as 0 to 25, 25 to 50, 50 to 75, 75 to 100 ( 구간에 해당하는 list 생성 )
bins = [0, 25, 50, 75, 100] 
group_names = ['Low', 'Okay', 'Good', 'Great'] # 각 구간에 대한 이름을 명명한다.
# Cut후 categories에 할당
categories = pd.cut(df['postTestScore'], bins, labels=group_names)
print(categories)
>>
0       Low
1     Great
2      Good
3      Good
4      Good
5       Low
6     Great
7      Good
8      Good
9      Good
10     Good
11     Good
Name: postTestScore, dtype: category
Categories (4, object): [Low < Okay < Good < Great]



# 기존 dataframe에 할당
df['categories'] = pd.cut(df['postTestScore'], bins, labels=group_names)
result = pd.value_counts(df['categories'])
print(result)
>> 
Good     8
Great    2
Low      2
Okay     0
Name: categories, dtype: int64



print(df)
>>     regiment company      name  preTestScore  postTestScore categories
0   Nighthawks     1st    Miller             4             25        Low
1   Nighthawks     1st  Jacobson            24             94      Great
2   Nighthawks     2nd       Ali            31             57       Good
3   Nighthawks     2nd    Milner             2             62       Good
4     Dragoons     1st     Cooze             3             70       Good
5     Dragoons     1st     Jacon             4             25        Low
6     Dragoons     2nd    Ryaner            24             94      Great
7     Dragoons     2nd      Sone            31             57       Good
8       Scouts     1st     Sloan             2             62       Good
9       Scouts     1st     Piger             3             70       Good
10      Scouts     2nd     Riani             2             62       Good
11      Scouts     2nd       Ali             3             70       Good

 

(4) Label encoding by slearn

- 위에서 배운 pandas의 getdummies()메소드 이 외에도 Sckit-learn의 preprocessing 패키지도 label, one-hot지원

raw_example = df.to_numpy()
print(raw_example[:3])
>> [['Nighthawks' '1st' 'Miller' 4 25 'Low']
    ['Nighthawks' '1st' 'Jacobson' 24 94 'Great']
    ['Nighthawks' '2nd' 'Ali' 31 57 'Good']]
import pandas as pd
import numpy as np
from sklearn import preprocessing

(상위 코드 생략)

le = preprocessing.LabelEncoder() # Encoder 생성
le.fit(raw_example[:,0])  # Data에 맞게 encoding fitting
result = le.transform(raw_example[:,0]) # 실제 데이터 -> labelling data
print(result)
>> [1 1 1 1 0 0 0 0 2 2 2 2]

- Label encoder의 fit과 transform의 과정이 나눠진 이유는 새로운 데이터 입력시, 기존 labelling규칙을 그대로 적용할 필요가 있기 때문이다.

- Fit 은 규칙을 생성하는 과정

- Transform은 규칙을 적용하는 과정

- Fit을 통해 규칙이 생성된 labelencoder는 따로 저장하여 새로운 데이터를 입력할 경우 사용할 수 있음

- Encoder들을 실제 시스템에 사용할 경우 pickle화 필요

label_column = [0, 1, 2, 5]
label_encoder_list = []
for column_idex in label_column:
    le = preprocessing.LabelEncoder()
    le.fit(raw_example[:, column_idex])
    data[:, column_idex] = le.transform(raw_example[:,column_idex])
    label_encoder_list.append(le) # 기존 label encoder를 따로 저장
    del le
print(data[:3])
>> [[1 0 4 4 25 2]
    [1 0 2 24 94 1]
    [1 1 0 31 57 0]]



print(label_encoder_list[0].transform(raw_example[:10,0])) # 저장된 le로 새로운 데이터에 적용
>> [1 1 1 1 0 0 0 0 2 2]

 

(5) One-hot encoding by sklearn

- Numerice labelling 이 완료된 데이터에 one-hot적용

- 데이터는 1-dim으로 변환하여 넣어 줄 것은 권장

one_hot_enc = preprocessing.OneHotEncoder()
one_hot_enc.fit(data[:,0].reshape(-1,1)) # 1-dim 변환하여 fit

onhotlables = one_hot_enc.transform(data[:,0].reshape(-1,1)).toarray() # 1-dim 변환후 transform -> ndarray
print(onhotlables)
>> [[0. 1. 0.]
   [0. 1. 0.]
   [0. 1. 0.]
   [0. 1. 0.]
   [1. 0. 0.]
   [1. 0. 0.]
   [1. 0. 0.]
   [1. 0. 0.]
   [0. 0. 1.]
   [0. 0. 1.]
   [0. 0. 1.]
   [0. 0. 1.]]

5) Feature scaling

https://github.com/TEAMLAB-Lecture/AI-python-connect/blob/master/codes/ch_2/5/5_feature_scaling.ipynb

- Feature간의 최대 - 최소값의 차이를 맞춘다.

- 실제 사용할 때는 반드시 정규화 Parameter(최대/최소, 평균/표준편차) 등을 기억하여 새로운 값에 적용해야함

(1) Min - Max Normalization

- 기존 변수에 범위를 새로운 최대-최소로 변경, 일반적으로 0과 1 사이 값으로 변경함

(df["A"] - df["A"].min()) / (df["A"].max()-df["A"].min() * (5-1)+1)

 

(2) Z-Score Normalization (Standardization)

- 기존 변수에 범위를 정규 분포로 변환, 실제 Min-Max의 값을 모를 때 활용 가능

df["B"] = (df["B"] - df["B"].mean()) \ (df["B"].std())

 

(3) Feature Scaling Function

def feture_scaling(df, scaling_strategy="min-max", column=None):
    if column == None:
        column = [column_name for column_name in df.columns]
    for column_name in column:
        if scaling_strategy == "min-max":
            df[column_name] = ( df[column_name] - df[column_name].min() ) /\
                            (df[column_name].max() - df[column_name].min()) 
        elif scaling_strategy == "z-score":
            df[column_name] = ( df[column_name] - \
                               df[column_name].mean() ) /\
                            (df[column_name].std() )
    return df

 

(4) Feature scaling with sklearn

- Label encoder와 마찬가지로, sklearn도 feature scale 지원

- MinMaxScaler와 StandarScaler 사용

from sklearn import preprocessing
std_scaler = preprocessing.StandardScaler().fit(df[['Alcohol', 'Malic acid']])
df_std = std_scaler.transform(df[['Alcohol', 'Malic acid']])
df_std[:5]

- Preprocessing은 모두 fit -> transform의 과정을 거침 (이유는 label encoder)와 동일

- 단, scaler는 한번에 여러 column을 처리 가능

minmax_scaler = preprocessing.MinMaxScaler().fit(df[['Alcohol', 'Malic acid']])
minmax_scaler.transform(df[['Alcohol', 'Malic acid']])
df_minmax[:3]

참고자료

https://www.edwith.org/aipython/lecture/24076/

 https://stackoverflow.com/questions/60164560/attributeerror-series-object-has-no-attribute-as-matrix-why-is-it-error

 

728x90