먼저, total_bedrooms 특성에 값이 없는 경우가 있는데, 이를 수정해보자. 방법에는 다음과 같이 세 가지가 있다.
- 해당 구역을 제거하기
- 전체 특성을 삭제하기
- 대체 : 누락된 값을 어떤 값으로 채우기
판다스의 dropana(), drop(), fillna() 메서드로 이런 작업을 간단하게 처리할 수 있다.
# housing.dropna(subset=["total_bedrooms"], inplace=True)
# housing.drop("total_bedrooms", axis=1, inplace=True)
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median, inplace=True)
여기까지는 저번 파트에서 다루었던 내용이다.
세 번째 옵션이 누락된 값을 중간값으로 채우는 코드이다.
위와 같은 동작을 사이킷런의 SimpleImputer 클래스를 통해 할 수 있다. 이 클래스는 각 특성의 중간값을 저장하고 있어 유용하다. 또한, 훈련 세트뿐만 아니라 검증 세트와 테스트 세트 그리고 모델에 주입될 새로운 데이터에 있는 누락된 값을 대체할 수 있다.
다음과 같이 누락된 값을 특성의 중간값으로 대체하도록 지정하여 SimpleImputer의 객체를 생성한다.
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
중간값은 수치형 특성에서만 계산될 수 있으므로 수치 특성만 가진 데이터 복사본을 생성한다.
housing_num = housing.select_dtypes(include=[np.number])
이후, imputer 객체의 fit() 메서드를 사용해 훈련 데이터에 적용할 수 있다.
imputer.fit(housing_num)
imputer은 각 특성의 중간값을 계산하여 그 결과를 객체의 statistics_ 속성에 저장한다.
이후, 학습된 impuer 객체를 사용해 훈련 세트에서 누락된 값을 학습된 중간값으로 바꿀 수 있다.
X = imputer.transform(housing_num)
누락된 값을 평균이나 가장 자주 등장하는 값, 상수로 바꾸는 것도 가능하다. 뒤 두 가지 방법은 수치가 아닌 데이터도 지원한다.
사이킷런 변환기는 판다스 데이터프레임이 입력되더라도 넘파이 배열이나 사이파이 희소 행렬을 출력한다. 따라서 imputer.transform(housing_num) 의 출력 또한 넘파이 배열이다.
type(X)
>> numpy.ndarray
현재 상태에서는 열 이름도 인덱스도 없기 때문에, 이를 데이터프레임으로 감싸서 열 이름과 인덱스를 복원할 수 있다.
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
index=housing_num.index)
위에서는 수치형 특성만을 다뤘고, 이제는 텍스트 특성을 살펴보자.
housing_cat = housing[["ocean_proximity"]]
housing_cat.head(8)
ocean_proximity
13096 NEAR BAY
14973 <1H OCEAN
3785 INLAND
14689 INLAND
20507 NEAR OCEAN
1286 INLAND
18078 <1H OCEAN
4396 NEAR BAY
housing_cat.value_counts()
ocean_proximity
<1H OCEAN 7274
INLAND 5301
NEAR OCEAN 2089
NEAR BAY 1846
ISLAND 2
Name: count, dtype: int64
이를 통해, 이 특성은 범주형 특성임을 알 수 있다.
이 카테고리를 텍스트에서 숫자로 변환하기 위해 사이킷런의 OrdinalEncoder을 사용한다.
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
인코딩된 몇 개의 값을 확인하면 다음과 같다.
housing_cat_encoded[:8]
array([[3.],
[0.],
[1.],
[1.],
[4.],
[1.],
[0.],
[3.]])
categories_ 인스턴스 변수를 사용해 카테고리 리스트를 얻을 수 있다. 범주형 특성마다 1D 카테고리 배열을 담은 리스트가 반환된다.
ordinal_encoder.categories_
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
이런 표현 방식의 문제는 머신러닝 알고리즘이 가까이 있는 두 값을 떨어져 있는 두 값보다 더 비슷하다고 생각한다는 점이다. (숫자 상으로 비슷하면, 실제로는 전혀 다른 특징을 가진 값이어도 비슷하게 인식하게 된다.)
이러한 문제는 카테고리별 이진 특성을 만들어 해결한다. 카테고리가 <1H OCEAN일 때 한 특성이 1이고, 카테고리가 INLAND일 때 다른 한 특성이 1이 되는 식이다.
한 특성만 1이고 나머지는 0이므로 이를 원-핫 인코딩이라고 부른다. 새로운 특성을 더미 특성이라고도 부른다. 사이킷런은 범주 값을 원-핫 벡터로 바꾸기 위한 OneHotEncoder 클래스를 제공한다.
from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
OneHotEncoder의 출력은 넘파이 배열이 아니라 사이파이 희소 행렬이다.
housing_cat_1hot
<16512x5 sparse matrix of type '<class 'numpy.float64'>'
with 16512 stored elements in Compressed Sparse Row format>
희소 행렬은 0이 대부분인 행렬을 매우 효율적으로 표현한다. 내부적으로 0이 아닌 값과 그 위치만 저장한다. 이는 많은 메모리를 절약하고 계산 속도를 높여주는 효과를 가진다. 공식 documentation
대부분 희소 행렬을 보통의 2D 배열처럼 사용할 수 있지만, 넘파이 배열로 바꾸려면 toarray() 메서드를 호출해야 한다.
housing_cat_1hot.toarray()
array([[0., 0., 0., 1., 0.],
[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
...,
[0., 0., 0., 0., 1.],
[1., 0., 0., 0., 0.],
[0., 0., 0., 0., 1.]])