결측치 (Not Available-NA, NaN, None, Null)
머신러닝 알고리즘은 데이터셋에 결측치가 있으면 학습이나 추론을 하지 못하기 때문에 적절한 처리가 필요하다.
결측치 처리방법
dataframe.isnull()
, dataframe.isna()
dataFrame.dropna(axis=0, subset=None, inplace=False)
dataframe.fillna(value)
sklearn.impute.SimpleImputer
transformer 클래스 사용import pandas as pd
import numpy as np
data = {
"col1":[10, np.nan, 30, 40],
"col2":['A', 'A', 'C', np.nan],
"col3":[10.5, 2.8, np.nan, 9.7]
}
df = pd.DataFrame(data)
df
# 결측치 확인
df.isna().sum()
col1 1
col2 1
col3 1
dtype: int64
#### 제거
df.dropna()
df.dropna(subset=['col1' ,'col3']
df.dropna(axis=1, subset=[1, 2])
#### 변경
df.fillna(10)
df.fillna({
"col1":1000,
"col2":"가"
})
from sklearn.impute import SimpleImputer
imputer_con = SimpleImputer(strategy="median")
imputer_cate = SimpleImputer(strategy="most_frequent")
# 연속형
r1 = imputer_con.fit_transform(df[['col1', 'col3']])
r1
array([[10. , 10.5],
[30. , 2.8],
[30. , 9.7],
[40. , 9.7]])
# 범주형
r2 = imputer_cate.fit_transform(df[['col2']])
r2
array([['A'],
['A'],
['C'],
['A']], dtype=object)
np.concatenate([r1, r2], axis=1)
array([[10.0, 10.5, 'A'],
[30.0, 2.8, 'A'],
[30.0, 9.7, 'C'],
[40.0, 9.7, 'A']], dtype=object)
의미 그대로 이상한 값, 튀는 값, 패턴을 벗어난 값으로 그 Feature를 가지는 대부분의 값들과는 동떨어진 값을 말한다.
오류값
극단치(분포에서 벋어난 값)
어떤 종류의 값을 모았는지에 따라 크게 범주형과 수치형으로 나눈다.
통계적으로 데이터형식을 나누는 기준은 여러가지가 있다.
범주형(Categorical) 변수
이산적(Discrete): 대상 값이 연속적이지 않고 떨어져 있는 형태
- 파이썬 데이터 타입별
- 실수형 데이터로 구성된 Feature는 연속형 값이다.
- 문자열 데이터로 구성된 Feature는 단순 문자열값이거나 범주형 값이다.
- 정수형 데이터로 구성된 Feature는 범주형이거나 일반 수치형(이산형) 값이다.
- 몇개의 고유값으로 구성되었는지를 봐야 한다.
범주형 Feature의 고유값들 오름차순 정렬 후 0 부터 1씩 증가하는 값으로 변환
숫자의 크기의 차이가 모델에 영향을 주지 않는 트리 계열 모델(의사결정나무, 랜덤포레스트)에 적용한다.
숫자의 크기의 차이가 모델에 영향을 미치는 선형 계열 모델(로지스틱회귀, SVM, 신경망)에는 사용하면 안된다.
sklearn.preprocessing.LabelEncoder 사용
items = ['TV', '냉장고', '컴퓨터', '컴퓨터', '냉장고', '에어콘', 'TV', '에어콘']
from sklearn.preprocessing import LabelEncoder
# LabelEncoder생성
le = LabelEncoder()
# 학습
le.fit(items)
# 변환
item_label = le.transform(items)
item_label
array([0, 1, 3, 3, 1, 2, 0, 2])
# 인코딩된 클래스를 확인
le.classes_
array(['TV', '냉장고', '에어콘', '컴퓨터'], dtype='<U3')
# 디코딩 (class(인코딩된 정수)->class name(원래 문자열))
le.inverse_transform([3, 2, 2, 1, 0, 0])
array(['컴퓨터', '에어콘', '에어콘', '냉장고', 'TV', 'TV'], dtype='<U3')
####### fit()과 transform()의 대상이 같은경우 -> fit_transform()
le2 = LabelEncoder()
item_label2 = le2.fit_transform(items)
print(item_label2)
print(le2.classes_)
[0 1 3 3 1 2 0 2]
['TV' '냉장고' '에어콘' '컴퓨터']
####### fit() 대상과 transform()대상이 다른경우
class_names = ["냉장고", "TV", "에어콘", "컴퓨터", "노트북", "스마트폰"]
le3 = LabelEncoder()
le3.fit(class_names)
print(le3.classes_)
['TV' '냉장고' '노트북' '스마트폰' '에어콘' '컴퓨터']
item_label3 = le3.transform(items)
print(item_label3)
[0 1 5 5 1 4 0 4]
# 학습대상에 없는 class를 변환하면 Exception 발생.
le3.transform(["컴퓨터", "녹차"])
cols = ['age', 'workclass','fnlwgt','education', 'education-num', 'marital-status', 'occupation','relationship', 'race', 'gender','capital-gain','capital-loss', 'hours-per-week','native-country', 'income']
import pandas as pd
data = pd.read_csv('data/adult.data',
header=None, # 첫번째 줄을 Data로 읽는다.
names=cols, # 컬럼명 지정
skipinitialspace=True,
na_values="?")
data.shape
(32561, 15)
data.head()
# 결측치를 제거
df = data.dropna()
data.shape, df.shape
((32561, 15), (30162, 15))
df['income'].value_counts()
income
<=50K 22654
>50K 7508
Name: count, dtype: int64
df['income'].value_counts(normalize=True)
income
<=50K 0.751078
>50K 0.248922
Name: proportion, dtype: float64
encoding_columns 컬럼들은 Label Encoding 처리,
not_encoding_columns 컬럼들의 값들은 그대로 유지.
encoding_columns의 값들은 LabelEncoding 된 값으로 not_encoding_columns의 값들은 원래값 그대로 구성된 DataFrame을 생성해서 반환한다.
주의: LabelEncoding은 Feature(컬럼) 별로 처리해야 한다. 한번에 여러컬럼을 하나의 LabelEncoder로 처리할 수 없다.
adult_df = df.copy()
from sklearn.preprocessing import LabelEncoder
encoding_class = ['workclass','education', 'marital-status', 'occupation','relationship', 'race', 'gender','native-country', 'income']
not_encoding_class = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week']
le4 = LabelEncoder()
for column in encoding_class:
adult_df[column] = le4.fit_transform(adult_df[column])
adult_df.head()
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
# adult_df 에서 X, y 분리
X = adult_df.drop(columns='income')
y = adult_df['income']
# Train set, Validation set, Test set 분리
X_tmp, X_test, y_tmp, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
X_train, X_val, y_train, y_val = train_test_split(X_tmp, y_tmp, test_size=0.2,
stratify=y_tmp, random_state=0)
print(X_train.shape, X_val.shape, X_test.shape)
print(y_train.shape, y_val.shape, y_test.shape)
(19303, 14) (4826, 14) (6033, 14)
(19303,) (4826,) (6033,)
max_depth = 7
# 모델 생성
tree = DecisionTreeClassifier(max_depth=max_depth, random_state=0)
# train(학습/훈련)
tree.fit(X_train, y_train)
# 검증 (train/val set)
## 추론
pred_train = tree.predict(X_train)
pred_val = tree.predict(X_val)
## 정확도 검증
train_acc = accuracy_score(y_train, pred_train)
val_acc = accuracy_score(y_val, pred_val)
# 검증결과 확인(출력)
print(f"max_depth: {max_depth}")
print(f"train 정확도: {train_acc}, validation 정확도: {val_acc}")
max_depth: 7
train 정확도: 0.8564471843754857, validation 정확도: 0.855781185246581
-> train 정확도 < validation 정확도 일 경우의 하이퍼파라미터를 사용하는 것이 더 좋다.
best_model = DecisionTreeClassifier(max_depth = 7, random_state = 0)
best_model.fit(X_train, y_train)
pred_test = best_model.predict(X_test)
accuracy_score(y_test, pred_test)
0.8488314271506713
import numpy as np
items=np.array(['TV','냉장고','전자렌지','컴퓨터','선풍기','선풍기','믹서','믹서', "냉장고"])
items.shape
(9,)
-> 데이터셋이 1차원 배열로 구성되어 있어서 원핫인코딩 처리가 불가능하다.
items = items[..., np.newaxis] # 더미 추가 / items.reshape(-1, 1) 가능
print(items.shape)
items
array([['TV'],
['냉장고'],
['전자렌지'],
['컴퓨터'],
['선풍기'],
['선풍기'],
['믹서'],
['믹서'],
['냉장고']], dtype='<U4')
from sklearn.preprocessing import OneHotEncoder
# OneHotEncoder 객체 생성
ohe = OneHotEncoder()
# ohe = OneHotEncoder(sparse_output=False) # 결과를 ndarray로 반환. True(기본값) : csr_matrix로 반환.
# 굳이 ndarray로 반환할 필요는 없으나 값을 확인하려면 변경해서 확인해야한다.
# 학습
ohe.fit(items)
# 변환
result = ohe.transform(items)
result.shape
(9,6)
ohe.get_feature_names_out()
array(['x0_TV', 'x0_냉장고', 'x0_믹서', 'x0_선풍기', 'x0_전자렌지', 'x0_컴퓨터'],
dtype=object)
result.toarray()
array([[1., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 0.],
[0., 0., 0., 0., 0., 1.],
[0., 0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0., 0.],
[0., 0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0., 0.],
[0., 1., 0., 0., 0., 0.]])
type(result)
scipy.sparse._csr.csr_matrix
pd.DataFrame(result.toarray(), columns=ohe.get_feature_names_out())
척도: 값을 측정하거나 평가하는 단위. ex) cm, km, kg
-> StandardScaler 와 Min Max Scaling 둘 중 어떤 Scaling을 사용해야 하는지에 대한 기준은 없다. 둘 다 해보고 결과를 보고 판단.(보통 StandardScaler 의 경우가 성능이 좋게 나옴)