본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 17. · 5 Views
피처 엔지니어링 기초 완벽 가이드
머신러닝 모델의 성능을 좌우하는 피처 엔지니어링의 핵심 개념을 학습합니다. 결측치 처리부터 인코딩 기법까지 실무에 바로 적용할 수 있는 기초를 다룹니다.
목차
1. 피처 엔지니어링이란
이제 막 데이터 분석팀에 합류한 김개발 씨는 첫 프로젝트를 맡았습니다. "모델 정확도를 높여보세요"라는 과제였습니다.
열심히 여러 알고리즘을 시도해봤지만 정확도는 60%에서 멈춰있었습니다. 그때 선배 박시니어 씨가 다가와 말했습니다.
"알고리즘을 바꾸기 전에 먼저 피처를 손봐야 해요."
피처 엔지니어링은 머신러닝 모델의 성능을 향상시키기 위해 원본 데이터를 가공하고 변환하는 과정입니다. 마치 요리사가 재료를 손질하듯이, 날것의 데이터를 모델이 잘 이해할 수 있는 형태로 다듬는 작업입니다.
좋은 피처 엔지니어링은 복잡한 알고리즘보다 더 큰 성능 향상을 가져올 수 있습니다.
다음 코드를 살펴봅시다.
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
# 고객 데이터 샘플
customer_data = pd.DataFrame({
'age': [25, 30, np.nan, 45, 35],
'income': [50000, 60000, 55000, np.nan, 70000],
'city': ['서울', '부산', '서울', '대구', '부산'],
'purchased': ['Yes', 'No', 'Yes', 'Yes', 'No']
})
# 피처 엔지니어링 전 데이터 확인
print("원본 데이터:\n", customer_data)
print("\n결측치 개수:\n", customer_data.isnull().sum())
김개발 씨는 당황했습니다. 알고리즘이 문제가 아니라고요?
지금까지 며칠 동안 여러 알고리즘을 시도했는데 말이죠. 박시니어 씨가 김개발 씨의 화면을 가리키며 설명을 시작했습니다.
"자, 여기 보이는 이 데이터들 있죠? 나이, 소득, 도시 이름...
이것들이 바로 피처입니다." 그렇다면 피처 엔지니어링이란 정확히 무엇일까요? 쉽게 비유하자면, 피처 엔지니어링은 마치 요리사가 재료를 손질하는 것과 같습니다.
훌륭한 요리사는 감자를 그대로 냄비에 넣지 않습니다. 껍질을 벗기고, 적당한 크기로 자르고, 때로는 미리 데치기도 합니다.
마찬가지로 데이터 과학자도 날것의 데이터를 그대로 모델에 넣지 않습니다. 모델이 잘 소화할 수 있는 형태로 변환해야 합니다.
피처 엔지니어링이 없던 시절에는 어땠을까요? 초기 머신러닝 연구자들은 데이터를 있는 그대로 모델에 입력했습니다.
결과는 참담했습니다. 같은 의미를 가진 데이터인데 표현 방식만 달라도 모델은 완전히 다른 것으로 인식했습니다.
더 큰 문제는 결측치나 이상치가 있을 때였습니다. 모델은 오류를 뱉거나 완전히 잘못된 패턴을 학습했습니다.
바로 이런 문제를 해결하기 위해 피처 엔지니어링이 등장했습니다. 피처 엔지니어링을 사용하면 모델의 학습 효율이 극적으로 향상됩니다.
또한 같은 알고리즘이라도 훨씬 더 정확한 예측을 할 수 있게 됩니다. 무엇보다 데이터의 노이즈를 줄이고 진짜 의미 있는 패턴을 찾아낼 수 있다는 큰 이점이 있습니다.
위의 코드를 살펴보면 실제 데이터가 어떤 상태인지 확인할 수 있습니다. 먼저 pandas로 고객 데이터를 생성했습니다.
실무에서 흔히 볼 수 있는 형태입니다. 나이, 소득, 도시, 구매 여부 같은 컬럼들이 있습니다.
그런데 자세히 보면 np.nan이 보입니다. 이것은 결측치입니다.
실무 데이터에서는 이런 결측치가 매우 흔합니다. 마지막 두 줄의 print문은 원본 데이터와 결측치 개수를 확인하는 코드입니다.
피처 엔지니어링을 시작하기 전에 데이터의 상태를 파악하는 것이 중요합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 이커머스 회사에서 고객의 구매 예측 모델을 만든다고 가정해봅시다. 원본 데이터에는 나이, 소득, 거주 도시, 과거 구매 이력 등이 있습니다.
이 데이터를 그대로 사용하면 모델 정확도가 60% 정도에 머물 수 있습니다. 하지만 피처 엔지니어링을 통해 연령대 그룹을 만들고, 소득 구간을 나누고, 도시를 숫자로 변환하면 정확도가 80%까지 올라갈 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 무작정 많은 피처를 만드는 것입니다.
피처가 너무 많으면 모델이 과적합되어 새로운 데이터에 대한 예측 성능이 떨어질 수 있습니다. 따라서 의미 있는 피처를 선별하여 적절한 수준으로 유지해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 데이터 전처리가 중요하다고 하는 거군요!" 피처 엔지니어링을 제대로 이해하면 모델의 성능을 획기적으로 향상시킬 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
알고리즘을 바꾸기 전에 먼저 피처를 살펴보는 습관을 들이면 좋습니다.
실전 팁
💡 - 피처 엔지니어링에 전체 개발 시간의 60-70%를 투자하세요
- 항상 원본 데이터를 백업하고 변환 과정을 기록하세요
- 도메인 지식을 활용하면 더 좋은 피처를 만들 수 있습니다
2. 결측치 처리 전략
김개발 씨가 데이터를 살펴보다가 이상한 점을 발견했습니다. 몇몇 고객의 나이나 소득 정보가 비어있었습니다.
"이거 데이터가 잘못 들어온 건가요?" 박시니어 씨가 웃으며 말했습니다. "아니에요, 실무에서는 이런 결측치가 당연히 있어요.
이걸 어떻게 처리하느냐가 중요하죠."
결측치는 데이터에서 값이 존재하지 않는 경우를 말합니다. 마치 퍼즐에서 몇 조각이 빠진 것과 같습니다.
결측치를 그냥 두면 모델이 학습할 수 없으므로, 삭제하거나 적절한 값으로 채워야 합니다. 상황에 따라 평균값, 중앙값, 최빈값으로 채우거나 아예 해당 행을 제거하는 등 다양한 전략을 사용할 수 있습니다.
다음 코드를 살펴봅시다.
import pandas as pd
import numpy as np
# 결측치가 있는 데이터
data = pd.DataFrame({
'age': [25, 30, np.nan, 45, 35, np.nan],
'income': [50000, 60000, 55000, np.nan, 70000, 65000],
'score': [85, 90, 88, 92, np.nan, 87]
})
# 전략 1: 결측치 행 삭제
data_dropped = data.dropna()
# 전략 2: 평균값으로 채우기
data_mean = data.fillna(data.mean())
# 전략 3: 특정 값으로 채우기
data_zero = data.fillna(0)
print("원본:\n", data)
print("\n삭제 후:\n", data_dropped)
print("\n평균값 채움:\n", data_mean)
김개발 씨는 의아했습니다. 데이터가 완벽하지 않다는 게 이상하게 느껴졌습니다.
하지만 박시니어 씨의 설명을 듣고 나서야 현실을 이해하게 되었습니다. "실무에서는 완벽한 데이터란 존재하지 않아요.
설문 조사에서 응답을 안 한 사람도 있고, 센서가 고장나서 데이터가 안 들어온 경우도 있죠." 결측치란 정확히 무엇일까요? 쉽게 비유하자면, 결측치는 마치 책의 일부 페이지가 찢어진 것과 같습니다.
이야기의 맥락을 이해하는 데 방해가 됩니다. 그 빈 페이지를 어떻게 처리할지 결정해야 합니다.
그냥 건너뛸 것인지, 아니면 앞뒤 문맥을 보고 내용을 추측해서 채울 것인지 말이죠. 결측치 처리가 없던 시절에는 어땠을까요?
초기 통계 분석에서는 결측치가 있는 데이터는 그냥 분석에서 제외했습니다. 간단했지만 문제가 많았습니다.
데이터의 상당 부분을 버리게 되면 샘플 크기가 줄어들어 분석 결과의 신뢰성이 떨어졌습니다. 더 큰 문제는 결측치가 랜덤하게 발생한 것이 아니라 특정 패턴을 가진 경우였습니다.
예를 들어 고소득자들이 소득을 밝히기 꺼려한다면, 이들을 제외하면 분석이 왜곡됩니다. 바로 이런 문제를 해결하기 위해 다양한 결측치 처리 전략이 등장했습니다.
결측치를 적절히 처리하면 데이터 손실을 최소화할 수 있습니다. 또한 모델이 안정적으로 학습할 수 있는 환경을 만들어줍니다.
무엇보다 데이터의 통계적 특성을 유지하면서도 완전한 데이터셋을 확보할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 결측치가 포함된 샘플 데이터를 만들었습니다. np.nan은 numpy에서 결측치를 나타내는 특수 값입니다.
그 다음 세 가지 처리 전략을 보여줍니다. dropna()는 결측치가 있는 행을 완전히 삭제합니다.
fillna(data.mean())은 각 컬럼의 평균값으로 결측치를 채웁니다. fillna(0)은 모든 결측치를 0으로 채웁니다.
각 전략은 장단점이 있습니다. 행 삭제는 간단하지만 데이터를 잃습니다.
평균값 채우기는 데이터를 보존하지만 분포를 왜곡할 수 있습니다. 0으로 채우기는 빠르지만 의미 없는 값을 넣는 것일 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 병원에서 환자 데이터를 분석한다고 가정해봅시다.
혈압 데이터에 결측치가 있다면 어떻게 할까요? 그 환자의 다른 진료 기록에서 평균 혈압을 계산해서 채울 수 있습니다.
또는 비슷한 나이와 성별을 가진 다른 환자들의 평균값을 사용할 수도 있습니다. 중요한 검사 결과가 빠진 경우라면 해당 환자 데이터를 아예 제외하는 것이 더 안전할 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 결측치를 무조건 평균값으로 채우는 것입니다.
이렇게 하면 데이터의 분산이 줄어들어 모델이 실제 변동성을 제대로 학습하지 못할 수 있습니다. 따라서 결측치의 발생 원인과 데이터의 특성을 고려해서 적절한 전략을 선택해야 합니다.
김개발 씨는 여러 전략을 시도해보았습니다. 놀랍게도 평균값으로 채운 데이터로 학습한 모델의 정확도가 70%까지 올라갔습니다.
"와, 이것만으로도 10%나 올랐네요!" 결측치 처리를 제대로 이해하면 데이터 손실 없이 완전한 데이터셋을 만들 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
결측치를 발견하면 당황하지 말고, 어떤 전략이 가장 적합할지 고민해보는 습관을 들이세요.
실전 팁
💡 - 결측치 비율이 30% 이상이면 해당 컬럼 자체를 제거하는 것도 고려하세요
- 수치형 데이터는 평균/중앙값, 범주형 데이터는 최빈값으로 채우세요
- 결측치 패턴을 분석하면 데이터 수집 과정의 문제를 발견할 수 있습니다
3. 범주형 변수 이해
이번에는 도시 컬럼이 문제였습니다. "서울", "부산", "대구" 같은 텍스트가 있었습니다.
김개발 씨가 이것을 그대로 모델에 넣으려고 하자 에러가 발생했습니다. "머신러닝 모델은 숫자만 이해할 수 있어요." 박시니어 씨가 말했습니다.
"텍스트를 숫자로 바꿔야 하는데, 이게 생각보다 까다로워요."
범주형 변수는 카테고리나 그룹을 나타내는 변수입니다. 성별, 도시명, 제품 등급처럼 정해진 몇 가지 값 중 하나를 가집니다.
마치 객관식 문제의 선택지와 같습니다. 머신러닝 모델은 숫자만 처리할 수 있으므로, 범주형 변수를 적절한 숫자 형태로 변환해야 합니다.
단순히 순서대로 번호를 매기면 의도하지 않은 순서 관계가 생길 수 있어 주의가 필요합니다.
다음 코드를 살펴봅시다.
import pandas as pd
# 범주형 변수를 포함한 데이터
product_data = pd.DataFrame({
'product_id': [1, 2, 3, 4, 5],
'category': ['전자제품', '의류', '식품', '전자제품', '의류'],
'size': ['S', 'M', 'L', 'M', 'S'],
'grade': ['Gold', 'Silver', 'Bronze', 'Gold', 'Silver']
})
# 범주형 변수 확인
print("데이터 타입:\n", product_data.dtypes)
print("\n범주형 변수의 고유값:")
print("카테고리:", product_data['category'].unique())
print("사이즈:", product_data['size'].unique())
print("등급:", product_data['grade'].unique())
김개발 씨는 당황했습니다. 왜 "서울"이라는 단어를 그대로 쓸 수 없는 걸까요?
사람은 쉽게 이해하는데 말이죠. 박시니어 씨가 친절히 설명했습니다.
"컴퓨터는 덧셈, 뺄셈, 곱셈 같은 수학 연산을 해요. '서울' 더하기 '부산'은 뭘까요?
의미가 없죠. 그래서 숫자로 바꿔야 해요." 범주형 변수란 정확히 무엇일까요?
쉽게 비유하자면, 범주형 변수는 마치 옷장의 서랍 라벨과 같습니다. "양말", "셔츠", "바지"처럼 각 서랍에는 이름이 붙어있습니다.
이 이름들은 순서가 없습니다. "양말"이 "셔츠"보다 크거나 작은 것이 아니죠.
마찬가지로 "서울"과 "부산"도 단순히 다른 카테고리일 뿐 크기를 비교할 수 없습니다. 범주형 변수 처리가 없던 시절에는 어땠을까요?
초기 머신러닝 시스템은 수치형 데이터만 다룰 수 있었습니다. 온도, 무게, 거리처럼 숫자로 표현되는 것들이었죠.
하지만 실제 세상의 많은 데이터는 범주형입니다. 사람들은 어쩔 수 없이 "서울=1, 부산=2, 대구=3" 이런 식으로 임의의 숫자를 할당했습니다.
문제는 모델이 이것을 진짜 숫자로 인식한다는 점이었습니다. 서울이 부산보다 작고, 대구가 가장 크다고 학습해버렸습니다.
바로 이런 문제를 해결하기 위해 범주형 변수의 개념이 명확히 정의되었습니다. 범주형 변수를 제대로 이해하면 데이터의 의미를 왜곡하지 않고 모델에 전달할 수 있습니다.
또한 적절한 인코딩 방법을 선택할 수 있게 됩니다. 무엇보다 순서가 있는 범주와 없는 범주를 구분해서 처리할 수 있다는 큰 이점이 있습니다.
위의 코드를 살펴보겠습니다. 먼저 제품 데이터를 만들었습니다.
category, size, grade 컬럼이 모두 범주형 변수입니다. dtypes로 데이터 타입을 확인하면 'object'로 표시됩니다.
이것은 pandas에서 문자열을 나타냅니다. unique() 메서드는 각 컬럼의 고유한 값들을 보여줍니다.
이를 통해 몇 개의 카테고리가 있는지 파악할 수 있습니다. 여기서 중요한 점은 size와 grade는 순서가 있다는 것입니다.
S < M < L이고, Bronze < Silver < Gold입니다. 이런 변수를 순서형 범주 변수라고 합니다.
반면 category는 순서가 없습니다. 이를 명목형 범주 변수라고 합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 은행에서 대출 승인 모델을 만든다고 가정해봅시다.
고객 데이터에는 직업(회사원, 자영업, 프리랜서), 학력(고졸, 대졸, 대학원졸), 거주지역(서울, 경기, 지방)이 있습니다. 학력은 순서가 있지만 직업과 거주지역은 순서가 없습니다.
각각 다른 방식으로 인코딩해야 합니다. 순서형은 숫자로 직접 매핑할 수 있지만, 명목형은 원핫인코딩 같은 방법을 써야 합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 범주형 변수를 똑같이 처리하는 것입니다.
순서가 있는 변수와 없는 변수를 구분하지 않으면 모델이 잘못된 관계를 학습할 수 있습니다. 따라서 데이터를 받으면 먼저 각 컬럼이 어떤 타입인지 정확히 파악해야 합니다.
김개발 씨는 노트에 정리했습니다. "명목형은 순서 없음, 순서형은 순서 있음.
각각 다르게 처리!" 범주형 변수를 제대로 이해하면 데이터의 특성에 맞는 인코딩 방법을 선택할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
데이터를 받으면 가장 먼저 범주형 변수를 찾아내고 분류하는 습관을 들이세요.
실전 팁
💡 - unique() 메서드로 범주의 개수를 먼저 확인하세요
- 순서형과 명목형을 구분하는 것이 중요합니다
- 범주가 너무 많으면(예: 100개 이상) 그룹핑을 고려하세요
4. 레이블 인코딩
"그럼 이제 텍스트를 숫자로 바꿔볼까요?" 박시니어 씨가 키보드를 두드렸습니다. 몇 줄의 코드만으로 "Gold", "Silver", "Bronze"가 0, 1, 2로 바뀌었습니다.
"와, 이렇게 간단해요?" 김개발 씨가 놀라워했습니다. "응, LabelEncoder를 쓰면 쉬워요.
하지만 조심해야 할 게 있어요."
레이블 인코딩은 범주형 변수를 정수로 변환하는 가장 단순한 방법입니다. 각 카테고리에 0부터 시작하는 숫자를 순서대로 할당합니다.
마치 출석번호를 매기는 것과 같습니다. 구현이 간단하고 메모리 효율적이지만, 순서가 없는 명목형 변수에 사용하면 의도하지 않은 순서 관계가 생길 수 있어 주의가 필요합니다.
다음 코드를 살펴봅시다.
from sklearn.preprocessing import LabelEncoder
import pandas as pd
# 등급 데이터 (순서가 있는 범주형)
grade_data = pd.DataFrame({
'customer': ['A', 'B', 'C', 'D', 'E'],
'grade': ['Gold', 'Silver', 'Bronze', 'Gold', 'Silver']
})
# LabelEncoder 생성 및 학습
label_encoder = LabelEncoder()
grade_data['grade_encoded'] = label_encoder.fit_transform(grade_data['grade'])
print("원본 데이터:\n", grade_data)
print("\n인코딩 매핑:", dict(zip(label_encoder.classes_,
label_encoder.transform(label_encoder.classes_))))
# 새로운 데이터 변환
new_grades = ['Silver', 'Gold']
print("\n새로운 데이터 변환:", label_encoder.transform(new_grades))
김개발 씨는 신기했습니다. 단 몇 줄의 코드로 텍스트가 숫자로 바뀌다니요.
하지만 박시니어 씨의 경고가 마음에 걸렸습니다. "레이블 인코딩은 양날의 검이에요.
잘 쓰면 편리하지만, 잘못 쓰면 큰 문제가 생겨요." 레이블 인코딩이란 정확히 무엇일까요? 쉽게 비유하자면, 레이블 인코딩은 마치 학생들에게 출석번호를 부여하는 것과 같습니다.
김철수는 1번, 이영희는 2번, 박민수는 3번. 간단하고 명확합니다.
하지만 이것이 키 순서나 성적 순서를 의미하지는 않습니다. 단지 식별을 위한 번호일 뿐입니다.
마찬가지로 레이블 인코딩도 카테고리에 번호를 매깁니다. 레이블 인코딩이 없던 시절에는 어땠을까요?
개발자들은 직접 딕셔너리를 만들어서 매핑했습니다. "Gold": 0, "Silver": 1, "Bronze": 2 이런 식으로요.
코드가 길어지고 실수하기 쉬었습니다. 새로운 카테고리가 추가되면 매번 딕셔너리를 수정해야 했습니다.
더 큰 문제는 학습 데이터와 테스트 데이터에 다른 매핑을 적용하는 실수였습니다. 바로 이런 문제를 해결하기 위해 LabelEncoder가 등장했습니다.
LabelEncoder를 사용하면 자동으로 일관된 매핑을 생성할 수 있습니다. 또한 새로운 데이터에 동일한 매핑을 적용할 수 있습니다.
무엇보다 코드가 간결해지고 실수할 여지가 줄어든다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 LabelEncoder 객체를 생성합니다. 그 다음 fit_transform() 메서드를 호출합니다.
fit은 데이터를 분석해서 매핑 규칙을 학습하는 것이고, transform은 실제로 변환하는 것입니다. 결과를 보면 Bronze=0, Gold=1, Silver=2로 알파벳 순서로 매핑되었습니다.
classes_ 속성은 인코더가 학습한 모든 카테고리를 보여줍니다. 나중에 새로운 데이터가 들어와도 같은 인코더 객체의 transform() 메서드를 사용하면 동일한 매핑이 적용됩니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 게임 회사에서 유저 등급 시스템을 분석한다고 가정해봅시다.
등급은 Bronze, Silver, Gold, Platinum, Diamond 순서입니다. 이것은 명확한 순서가 있는 범주형 변수입니다.
레이블 인코딩을 적용하면 0, 1, 2, 3, 4로 변환됩니다. 모델은 숫자가 클수록 높은 등급임을 자연스럽게 학습합니다.
이런 경우 레이블 인코딩이 매우 적합합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 도시명이나 제품 카테고리처럼 순서가 없는 변수에도 레이블 인코딩을 사용하는 것입니다. "서울"=0, "부산"=1, "대구"=2로 인코딩하면 모델이 서울 < 부산 < 대구라는 잘못된 관계를 학습할 수 있습니다.
따라서 순서가 없는 명목형 변수에는 원핫인코딩을 사용해야 합니다. 김개발 씨는 테스트해보았습니다.
등급 데이터에 레이블 인코딩을 적용하니 모델이 등급 순서를 제대로 이해했습니다. "순서가 있을 때만 쓰면 되겠네요!" 레이블 인코딩을 제대로 이해하면 순서형 범주 변수를 효율적으로 처리할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. 하지만 항상 데이터의 순서 관계를 먼저 확인하는 습관을 들이세요.
실전 팁
💡 - 순서가 명확한 범주형 변수에만 사용하세요
- fit_transform()은 학습용, transform()은 테스트용 데이터에 사용하세요
- 알파벳 순서로 매핑되므로 원하는 순서와 다를 수 있습니다
5. 원핫 인코딩
"그럼 도시명은 어떻게 바꿔요?" 김개발 씨가 물었습니다. "서울, 부산, 대구는 순서가 없잖아요." 박시니어 씨가 고개를 끄덕였습니다.
"맞아요. 이런 경우에는 원핫인코딩을 써야 해요.
각 도시를 별도의 컬럼으로 만드는 거죠. 좀 복잡해 보이지만 매우 중요해요."
원핫 인코딩은 명목형 범주 변수를 여러 개의 이진 컬럼으로 변환하는 방법입니다. 각 카테고리마다 하나의 컬럼을 만들고, 해당하는 컬럼만 1로 표시하고 나머지는 0으로 표시합니다.
마치 객관식 답안지에 하나만 체크하는 것과 같습니다. 순서 관계가 생기지 않아 명목형 변수에 적합하지만, 카테고리가 많으면 컬럼 수가 크게 증가하는 단점이 있습니다.
다음 코드를 살펴봅시다.
from sklearn.preprocessing import OneHotEncoder
import pandas as pd
import numpy as np
# 도시 데이터 (순서가 없는 범주형)
city_data = pd.DataFrame({
'customer_id': [1, 2, 3, 4, 5],
'city': [['서울'], ['부산'], ['서울'], ['대구'], ['부산']]
})
# OneHotEncoder 생성 및 학습
onehot_encoder = OneHotEncoder(sparse_output=False)
city_encoded = onehot_encoder.fit_transform(city_data[['city']])
# 결과를 DataFrame으로 변환
city_df = pd.DataFrame(
city_encoded,
columns=onehot_encoder.get_feature_names_out(['city'])
)
print("원본:\n", city_data)
print("\n원핫 인코딩 결과:\n", city_df)
print("\n최종 데이터:\n", pd.concat([city_data, city_df], axis=1))
김개발 씨는 결과를 보고 놀랐습니다. 하나의 city 컬럼이 세 개의 컬럼으로 늘어났습니다.
city_서울, city_부산, city_대구. 각 행마다 하나만 1이고 나머지는 0이었습니다.
"이게 바로 원핫 인코딩이에요." 박시니어 씨가 설명했습니다. "각 도시가 독립적인 특성이 되는 거죠." 원핫 인코딩이란 정확히 무엇일까요?
쉽게 비유하자면, 원핫 인코딩은 마치 설문조사의 복수 선택 문항과 같습니다. "해당하는 항목에 모두 체크하세요: 서울 ☐ 부산 ☐ 대구 ☐" 하지만 각 사람은 딱 하나의 도시에만 체크합니다.
서울 사람은 "서울 ☑ 부산 ☐ 대구 ☐"처럼 표시하는 것이죠. 이렇게 하면 세 도시 간에 어떤 크기 관계도 생기지 않습니다.
원핫 인코딩이 없던 시절에는 어땠을까요? 개발자들은 어쩔 수 없이 도시에 임의의 숫자를 할당했습니다.
서울=1, 부산=2, 대구=3 이런 식으로요. 문제는 모델이 이것을 진짜 순서로 해석한다는 점이었습니다.
"대구가 서울의 3배야!"라고 착각하는 것이죠. 심지어 서울과 부산의 평균을 계산해서 1.5라는 의미 없는 값을 만들어내기도 했습니다.
바로 이런 문제를 해결하기 위해 원핫 인코딩이 등장했습니다. 원핫 인코딩을 사용하면 카테고리 간에 순서 관계가 생기지 않습니다.
또한 각 카테고리가 독립적인 특성으로 취급되어 모델이 올바른 패턴을 학습할 수 있습니다. 무엇보다 명목형 변수의 의미를 왜곡하지 않는다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 OneHotEncoder 객체를 생성할 때 sparse_output=False를 지정했습니다.
기본값은 True인데, 그러면 희소 행렬이 반환되어 보기 어렵습니다. False로 하면 일반 배열로 반환됩니다.
fit_transform()으로 변환한 결과는 numpy 배열이므로, get_feature_names_out()으로 컬럼 이름을 가져와서 DataFrame으로 만들었습니다. 결과를 보면 서울인 행은 city_서울=1.0이고 나머지는 0.0입니다.
이렇게 딱 하나만 1이고 나머지는 0이어서 "원핫(One-Hot)"이라는 이름이 붙었습니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 온라인 쇼핑몰에서 상품 카테고리를 분석한다고 가정해봅시다. 카테고리는 "전자제품", "의류", "식품", "가구", "도서" 등입니다.
이것들은 순서가 전혀 없습니다. 원핫 인코딩을 적용하면 각 카테고리가 독립적인 컬럼이 됩니다.
모델은 "전자제품을 사는 고객은 같이 이런 특성이 있구나", "의류를 사는 고객은 저런 특성이 있구나"를 각각 학습할 수 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 카테고리가 너무 많은 변수에 원핫 인코딩을 적용하는 것입니다. 예를 들어 전국 250개 시군구를 원핫 인코딩하면 250개의 컬럼이 생깁니다.
이것은 차원의 저주를 일으켜 모델 성능을 오히려 떨어뜨릴 수 있습니다. 이럴 때는 상위 그룹(시도 단위)으로 묶거나 빈도가 낮은 카테고리를 "기타"로 합치는 것이 좋습니다.
김개발 씨는 도시 데이터에 원핫 인코딩을 적용했습니다. 모델 정확도가 75%까지 올랐습니다.
"레이블 인코딩보다 더 좋네요!" 원핫 인코딩을 제대로 이해하면 명목형 범주 변수를 올바르게 처리할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
순서가 없는 변수는 항상 원핫 인코딩을 고려하는 습관을 들이세요.
실전 팁
💡 - 순서가 없는 명목형 변수에 사용하세요
- 카테고리가 10개 이하일 때 효과적입니다
- 카테고리가 많으면 그룹핑을 먼저 고려하세요
6. get dummies 활용
김개발 씨가 코드를 보며 고개를 갸우뚱했습니다. "OneHotEncoder는 좀 복잡한데, 더 간단한 방법은 없나요?" 박시니어 씨가 웃으며 말했습니다.
"pandas에 get_dummies()라는 편리한 함수가 있어요. 한 줄이면 끝나죠.
실무에서 가장 많이 쓰는 방법이에요."
**get_dummies()**는 pandas에서 제공하는 원핫 인코딩 함수입니다. sklearn의 OneHotEncoder보다 훨씬 간단하게 사용할 수 있습니다.
마치 자동 번역기처럼 범주형 컬럼을 자동으로 찾아서 원핫 인코딩해줍니다. DataFrame을 입력하면 변환된 DataFrame을 바로 반환하므로 코드가 매우 간결해집니다.
빠른 프로토타이핑이나 데이터 탐색에 특히 유용합니다.
다음 코드를 살펴봅시다.
import pandas as pd
# 여러 범주형 변수를 포함한 데이터
customer_data = pd.DataFrame({
'age': [25, 30, 35, 40, 45],
'city': ['서울', '부산', '서울', '대구', '부산'],
'membership': ['Gold', 'Silver', 'Bronze', 'Gold', 'Silver'],
'purchased': ['Yes', 'No', 'Yes', 'Yes', 'No']
})
# get_dummies로 간단히 원핫 인코딩
encoded_data = pd.get_dummies(customer_data, columns=['city', 'membership'])
print("원본 데이터:\n", customer_data)
print("\nget_dummies 결과:\n", encoded_data)
# drop_first=True로 다중공선성 방지
encoded_drop = pd.get_dummies(customer_data, columns=['city'], drop_first=True)
print("\ndrop_first=True 결과:\n", encoded_drop)
김개발 씨는 get_dummies()를 실행하고 감탄했습니다. 정말로 한 줄이면 끝났습니다.
OneHotEncoder처럼 객체를 만들고 fit하고 transform할 필요가 없었습니다. "실무에서는 빠르게 테스트해야 할 때가 많아요." 박시니어 씨가 말했습니다.
"그럴 때 get_dummies()가 정말 유용하죠." get_dummies()란 정확히 무엇일까요? 쉽게 비유하자면, get_dummies()는 마치 전자동 세탁기와 같습니다.
OneHotEncoder는 일반 세탁기처럼 물 온도, 세탁 시간, 헹굼 횟수를 일일이 설정해야 합니다. 반면 get_dummies()는 "표준 세탁" 버튼 하나만 누르면 알아서 다 해줍니다.
물론 세밀한 조정은 어렵지만, 대부분의 경우에는 충분합니다. get_dummies()가 없던 시절에는 어땠을까요?
pandas 초기 버전에서는 원핫 인코딩을 하려면 sklearn을 import하고 복잡한 과정을 거쳐야 했습니다. 특히 여러 컬럼을 한 번에 처리하려면 반복문을 돌려야 했습니다.
코드가 길어지고 실수하기 쉬었습니다. 초보자들은 이 과정에서 좌절하기도 했습니다.
바로 이런 문제를 해결하기 위해 get_dummies()가 등장했습니다. get_dummies()를 사용하면 코드가 극적으로 간결해집니다.
또한 여러 컬럼을 한 번에 처리할 수 있습니다. 무엇보다 pandas DataFrame을 입력받아 DataFrame을 반환하므로 데이터 처리 파이프라인에 자연스럽게 통합된다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 첫 번째 get_dummies() 호출에서는 columns 파라미터로 변환할 컬럼을 지정했습니다.
city와 membership 두 컬럼이 모두 원핫 인코딩되었습니다. 결과를 보면 city_서울, city_부산, city_대구, membership_Bronze, membership_Gold, membership_Silver 컬럼이 생성되었습니다.
두 번째 호출에서는 drop_first=True 옵션을 사용했습니다. 이것은 각 범주형 변수의 첫 번째 카테고리를 제거합니다.
왜 그럴까요? 세 개의 도시가 있을 때, 서울도 부산도 아니면 자동으로 대구입니다.
따라서 city_대구 컬럼은 사실 불필요합니다. 이를 제거하면 다중공선성 문제를 방지할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 부동산 가격 예측 모델을 만든다고 가정해봅시다.
데이터에는 지역, 건물 유형, 방향 등 여러 범주형 변수가 있습니다. get_dummies()로 한 번에 모두 변환할 수 있습니다.
빠르게 모델을 학습시켜보고 어떤 특성이 중요한지 확인할 수 있습니다. 프로토타입을 만들 때 특히 유용합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 학습 데이터와 테스트 데이터를 따로 get_dummies()로 변환하는 것입니다.
이렇게 하면 두 데이터셋의 컬럼이 달라질 수 있습니다. 예를 들어 학습 데이터에는 "서울", "부산", "대구"가 있지만 테스트 데이터에는 "서울", "부산"만 있다면 컬럼 수가 안 맞습니다.
이럴 때는 학습 데이터로 OneHotEncoder를 fit한 후 테스트 데이터에 transform하는 것이 안전합니다. 김개발 씨는 여러 범주형 변수를 한 번에 변환했습니다.
"정말 편하네요! 개발 속도가 훨씬 빨라졌어요." get_dummies()를 제대로 이해하면 빠르게 데이터를 탐색하고 프로토타입을 만들 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. 다만 실제 배포용 모델에는 OneHotEncoder를 사용해서 학습-테스트 일관성을 보장하는 것이 좋습니다.
실전 팁
💡 - 빠른 프로토타이핑에 매우 유용합니다
- drop_first=True로 다중공선성을 방지하세요
- 실제 배포용에는 OneHotEncoder 사용을 고려하세요
7. 피처 선택 기초
모든 피처를 변환하고 나니 컬럼이 30개가 넘었습니다. 김개발 씨는 뿌듯했지만, 박시니어 씨는 걱정스러운 표정이었습니다.
"피처가 너무 많으면 오히려 성능이 떨어질 수 있어요. 중요한 피처만 선택하는 것도 피처 엔지니어링의 중요한 부분이에요."
피처 선택은 모델 성능에 중요한 피처만 골라내고 불필요한 피처는 제거하는 과정입니다. 마치 짐을 쌀 때 필요한 것만 챙기는 것과 같습니다.
피처가 너무 많으면 모델이 과적합되고 학습 시간도 길어집니다. 상관관계 분석, 피처 중요도, 통계적 방법 등을 사용해서 중요한 피처를 선별할 수 있습니다.
좋은 피처 선택은 모델 성능과 효율성을 동시에 향상시킵니다.
다음 코드를 살펴봅시다.
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectKBest, f_classif
# 샘플 데이터 생성
np.random.seed(42)
X = pd.DataFrame({
'age': np.random.randint(20, 60, 100),
'income': np.random.randint(30000, 100000, 100),
'score': np.random.randint(60, 100, 100),
'random_noise': np.random.randn(100) # 의미 없는 피처
})
y = (X['age'] > 40).astype(int) # 나이가 40 이상이면 1
# 방법 1: 상관관계 분석
correlation = X.corrwith(pd.Series(y))
print("피처-타겟 상관관계:\n", correlation.abs().sort_values(ascending=False))
# 방법 2: SelectKBest로 상위 K개 선택
selector = SelectKBest(f_classif, k=2)
X_selected = selector.fit_transform(X, y)
selected_features = X.columns[selector.get_support()]
print("\n선택된 피처:", list(selected_features))
김개발 씨는 의아했습니다. 피처가 많으면 많을수록 좋은 것 아닌가요?
더 많은 정보를 주는 것인데요. 박시니어 씨가 고개를 저었습니다.
"그렇지 않아요. 의미 없는 피처가 많으면 모델이 노이즈에 혼란스러워해요.
마치 시끄러운 카페에서 대화하려는 것과 같죠." 피처 선택이란 정확히 무엇일까요? 쉽게 비유하자면, 피처 선택은 마치 여행 짐을 싸는 것과 같습니다.
처음에는 이것저것 다 챙기고 싶습니다. 하지만 가방이 너무 무거우면 여행이 힘들어집니다.
정말 필요한 것만 골라야 합니다. 칫솔, 옷, 충전기는 필수지만 집에 있는 모든 책을 가져갈 필요는 없습니다.
마찬가지로 모델에도 정말 유용한 피처만 넣어야 합니다. 피처 선택이 없던 시절에는 어땠을까요?
초기 머신러닝에서는 "많을수록 좋다"는 생각이 지배적이었습니다. 모든 가능한 피처를 다 넣었습니다.
결과는 참담했습니다. 학습 데이터에서는 높은 정확도를 보였지만 새로운 데이터에서는 형편없었습니다.
이것이 바로 과적합입니다. 또한 피처가 수백 개, 수천 개가 되면서 학습 시간이 엄청나게 길어졌습니다.
바로 이런 문제를 해결하기 위해 피처 선택 기법이 등장했습니다. 피처 선택을 사용하면 모델의 일반화 성능이 향상됩니다.
또한 학습 시간이 단축되고 모델 해석이 쉬워집니다. 무엇보다 노이즈를 제거해서 진짜 의미 있는 패턴에 집중할 수 있다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 의도적으로 의미 없는 피처(random_noise)를 포함한 데이터를 만들었습니다.
타겟 변수 y는 나이가 40 이상인지 여부입니다. 따라서 age 피처가 가장 중요할 것입니다.
첫 번째 방법은 상관관계 분석입니다. corrwith()로 각 피처와 타겟의 상관관계를 계산했습니다.
결과를 보면 age가 가장 높은 상관관계를 가집니다. random_noise는 거의 0에 가까울 것입니다.
두 번째 방법은 SelectKBest를 사용한 자동 선택입니다. k=2로 설정해서 상위 2개 피처만 선택했습니다.
f_classif는 분류 문제에서 사용하는 통계적 기준입니다. get_support()로 어떤 피처가 선택되었는지 확인할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 고객 이탈 예측 모델을 만든다고 가정해봅시다.
처음에는 고객의 모든 정보를 피처로 만들었습니다. 나이, 성별, 가입일, 최근 로그인 날짜, 구매 횟수, 평균 구매 금액, 클릭 수, 체류 시간 등 50개가 넘었습니다.
피처 선택을 통해 정말 이탈과 관련 있는 10개 피처만 추렸습니다. 결과적으로 모델 정확도는 유지하면서 학습 시간은 절반으로 줄었습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 너무 공격적으로 피처를 제거하는 것입니다.
상관관계가 낮다고 해서 무조건 제거하면 안 됩니다. 여러 피처가 조합되었을 때 의미를 가질 수도 있습니다.
따라서 피처 선택 후에는 항상 모델 성능을 확인하고, 성능이 떨어지면 더 많은 피처를 포함하는 것을 고려해야 합니다. 또 다른 주의점은 피처 선택이 데이터에 의존적이라는 것입니다.
학습 데이터에서 선택한 피처가 테스트 데이터에서도 중요하다는 보장은 없습니다. 따라서 교차 검증을 통해 안정적인 피처 선택이 이루어지도록 해야 합니다.
김개발 씨는 피처 선택을 적용했습니다. 30개 피처 중 10개만 선택했더니 정확도는 거의 같았지만 학습 시간이 크게 줄었습니다.
"이제 피처 엔지니어링의 전체 흐름이 보이네요!" 피처 선택을 제대로 이해하면 효율적이고 강건한 모델을 만들 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
무작정 많은 피처를 만들기보다는, 질 높은 소수의 피처를 선별하는 습관을 들이세요. 마지막으로 박시니어 씨가 당부했습니다.
"피처 엔지니어링은 예술이에요. 정답은 없지만, 데이터를 깊이 이해하고 도메인 지식을 활용하면 훌륭한 피처를 만들 수 있어요.
계속 연습하세요!" 김개발 씨는 고개를 끄덕였습니다. 이제 피처 엔지니어링의 기초를 탄탄히 다졌습니다.
앞으로 더 복잡한 기법들을 배우겠지만, 오늘 배운 기초가 든든한 바탕이 될 것입니다.
실전 팁
💡 - 상관관계가 0.7 이상인 피처들은 중복일 가능성이 높습니다
- 피처 선택 후 반드시 모델 성능을 검증하세요
- 도메인 지식을 활용하면 자동 선택보다 더 좋은 결과를 얻을 수 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.