이미지 로딩 중...
AI Generated
2025. 11. 25. · 9 Views
데이터 타입 변환 및 정규화 완벽 가이드
데이터 분석과 머신러닝에서 가장 기초가 되는 데이터 타입 변환과 정규화 기법을 배워봅니다. 실무에서 자주 마주치는 데이터 전처리 문제를 Python으로 쉽게 해결하는 방법을 알려드립니다.
목차
- 문자열을_숫자로_변환하기
- 날짜_데이터_변환하기
- 카테고리_타입으로_메모리_절약하기
- Min-Max_정규화로_범위_맞추기
- Z-Score_표준화로_정규분포_만들기
- 불리언_타입_변환과_활용
- 로그_변환으로_왜곡된_분포_바로잡기
- 원-핫_인코딩으로_범주형_데이터_변환하기
- 결측값_처리_전략
- 이상치_탐지와_처리
1. 문자열을_숫자로_변환하기
시작하며
여러분이 엑셀 파일이나 CSV 데이터를 불러왔는데, 분명히 숫자여야 할 "나이"나 "가격" 컬럼이 계산이 안 되는 경험을 해본 적 있나요? 숫자끼리 더하려고 했는데 이상한 결과가 나오거나 에러가 발생하는 상황 말이에요.
이런 문제는 실제 개발 현장에서 정말 자주 발생합니다. 데이터를 수집할 때 숫자가 문자열 형태로 저장되는 경우가 많거든요.
예를 들어 "25"는 숫자 25가 아니라 그냥 글자 "2"와 "5"가 붙어있는 것으로 인식됩니다. 바로 이럴 때 필요한 것이 타입 변환입니다.
마치 외국어를 우리말로 번역하듯이, 문자열로 된 숫자를 진짜 숫자로 바꿔주는 작업이에요.
개요
간단히 말해서, 타입 변환은 데이터의 옷을 갈아입히는 것과 같습니다. "25"라는 문자열에서 따옴표를 벗기고 진짜 숫자 25로 만들어주는 거죠.
왜 이게 필요할까요? 컴퓨터는 문자열 "25"와 숫자 25를 완전히 다르게 취급합니다.
문자열 "25" + "30"은 "2530"이 되지만, 숫자 25 + 30은 55가 됩니다. 데이터 분석을 하려면 당연히 숫자로 계산해야겠죠?
예를 들어, 쇼핑몰에서 고객 데이터를 분석할 때 "구매금액" 컬럼이 문자열이면 평균 구매금액을 계산할 수 없습니다. 반드시 숫자로 변환해야 합니다.
Python의 Pandas 라이브러리는 이런 변환을 아주 쉽게 해줍니다. astype() 함수 하나로 원하는 타입으로 변환할 수 있고, to_numeric() 함수는 변환이 안 되는 값도 안전하게 처리해줍니다.
코드 예제
import pandas as pd
# 문자열로 된 데이터 생성
data = {'나이': ['25', '30', '28', 'unknown'],
'가격': ['1000', '2500', '1800', '3000']}
df = pd.DataFrame(data)
# 안전하게 숫자로 변환 (변환 불가능한 값은 NaN 처리)
df['나이'] = pd.to_numeric(df['나이'], errors='coerce')
# 직접 타입 지정하여 변환
df['가격'] = df['가격'].astype(int)
# 결과 확인
print(df)
print(f"평균 나이: {df['나이'].mean()}")
설명
이것이 하는 일: 문자열 형태의 숫자 데이터를 실제 계산 가능한 숫자 타입으로 변환합니다. 첫 번째로, 우리는 문자열로 된 데이터를 담은 DataFrame을 만들었습니다.
'나이' 컬럼에는 일부러 'unknown'이라는 변환 불가능한 값을 넣었어요. 실제 데이터에서 이런 상황이 정말 많거든요.
그 다음으로, pd.to_numeric() 함수를 사용했습니다. errors='coerce' 옵션이 핵심인데요, 이건 "변환이 안 되면 그냥 NaN(비어있음)으로 처리해줘"라는 뜻입니다.
덕분에 'unknown' 같은 값이 있어도 에러 없이 진행됩니다. 마지막으로, astype(int)를 사용해서 '가격' 컬럼을 정수로 바꿨습니다.
이 방법은 모든 값이 변환 가능할 때 사용하면 깔끔합니다. 여러분이 이 코드를 사용하면 더러운 데이터도 안전하게 숫자로 바꿀 수 있고, 바로 평균이나 합계 같은 계산을 할 수 있게 됩니다.
실전 팁
💡 데이터에 변환 불가능한 값이 섞여있을 수 있으니 항상 errors='coerce' 옵션을 먼저 시도해보세요
💡 변환 후에는 df.dtypes로 타입이 제대로 바뀌었는지 꼭 확인하세요
💡 NaN 값이 생겼다면 fillna()로 0이나 평균값으로 채워줄 수 있어요
💡 큰 숫자는 int 대신 float를 사용하면 메모리를 아낄 수 있습니다
2. 날짜_데이터_변환하기
시작하며
"2024-01-15"라는 날짜가 있는데, 이게 몇 월인지, 무슨 요일인지 알고 싶을 때 어떻게 하시나요? 문자열 상태에서는 이런 정보를 뽑아내기가 정말 어렵습니다.
날짜 데이터는 데이터 분석에서 가장 다루기 까다로운 데이터 중 하나입니다. "2024/01/15", "01-15-2024", "15 Jan 2024" 등 형식도 제각각이고, 시간대 문제까지 있으면 머리가 아파지죠.
바로 이럴 때 pd.to_datetime() 함수가 마법처럼 도와줍니다. 거의 모든 형식의 날짜를 자동으로 인식해서 변환해주거든요.
개요
간단히 말해서, 날짜 변환은 달력을 읽을 수 있게 만드는 것입니다. 문자열 "2024-01-15"를 컴퓨터가 "아, 이건 2024년 1월 15일 월요일이구나!"라고 이해하게 만들어주는 거예요.
왜 이게 중요할까요? 날짜 타입으로 변환하면 "이번 달 매출", "지난 주 가입자", "월요일 방문자" 같은 시간 기반 분석이 가능해집니다.
문자열 상태에서는 이런 분석이 불가능해요. 예를 들어, 쇼핑몰에서 "금요일에 매출이 가장 높다"는 인사이트를 찾으려면 날짜에서 요일을 추출해야 합니다.
날짜 타입이면 .dt.dayofweek 한 줄로 끝나지만, 문자열이면 복잡한 코드를 써야 해요. Pandas의 datetime 타입은 년, 월, 일, 요일, 분기 등 다양한 정보를 쉽게 추출할 수 있게 해줍니다.
코드 예제
import pandas as pd
# 여러 형식의 날짜 데이터
data = {'가입일': ['2024-01-15', '2024/02/20', '15-Mar-2024'],
'이름': ['김철수', '이영희', '박민수']}
df = pd.DataFrame(data)
# 자동으로 날짜 형식 인식하여 변환
df['가입일'] = pd.to_datetime(df['가입일'])
# 날짜에서 유용한 정보 추출
df['가입월'] = df['가입일'].dt.month
df['가입요일'] = df['가입일'].dt.day_name()
df['분기'] = df['가입일'].dt.quarter
print(df)
설명
이것이 하는 일: 문자열 날짜를 datetime 타입으로 바꾸고, 거기서 월, 요일 같은 정보를 추출합니다. 첫 번째로, 일부러 세 가지 다른 형식의 날짜를 준비했습니다.
'2024-01-15', '2024/02/20', '15-Mar-2024' 모두 다르죠? 실제 데이터에서 이런 일이 정말 많이 발생합니다.
그 다음으로, pd.to_datetime() 함수가 이 모든 형식을 자동으로 인식해서 변환합니다. 정말 똑똑하죠?
대부분의 날짜 형식을 알아서 처리해줍니다. 세 번째로, .dt 접근자를 사용해서 다양한 정보를 뽑아냈습니다.
.dt.month는 월을, .dt.day_name()은 요일 이름을, .dt.quarter는 분기를 알려줍니다. 여러분이 이 코드를 사용하면 "분기별 매출 비교", "요일별 방문자 패턴" 같은 시간 기반 분석을 쉽게 할 수 있습니다.
실전 팁
💡 날짜 형식이 특이하면 format 파라미터로 직접 지정할 수 있어요 (예: format='%Y년%m월%d일')
💡 변환 실패 시 errors='coerce'를 쓰면 NaT(날짜 버전의 NaN)로 처리됩니다
💡 시간대가 중요하면 tz 파라미터로 시간대를 지정하세요
💡 날짜 간 차이는 빼기 연산으로 바로 계산됩니다 (예: df['가입일'] - df['생년월일'])
3. 카테고리_타입으로_메모리_절약하기
시작하며
데이터에 "남성", "여성", "남성", "여성"... 이렇게 같은 값이 계속 반복되는 컬럼이 있나요?
이런 데이터를 문자열로 저장하면 엄청난 메모리 낭비입니다. 실제로 대용량 데이터를 다루다 보면 메모리 부족 에러를 만나는 경우가 많습니다.
특히 "성별", "지역", "등급" 같이 정해진 값만 반복되는 컬럼이 많을수록 문제가 심해지죠. 바로 이럴 때 category 타입이 해결책입니다.
마치 사전에 단어를 등록해놓고 번호로 참조하는 것처럼, 메모리를 획기적으로 줄여줍니다.
개요
간단히 말해서, category 타입은 반복되는 값에 번호표를 붙이는 것입니다. "남성"이라는 글자를 100만 번 저장하는 대신, "남성=1"이라고 정해놓고 숫자 1만 100만 번 저장하는 거예요.
왜 이게 효과적일까요? 문자열 "남성"은 6바이트가 필요하지만, 숫자 1은 단 1바이트면 됩니다.
데이터가 클수록 이 차이가 어마어마해집니다. 예를 들어, 100만 명의 고객 데이터에서 "지역" 컬럼이 17개 시도만 반복된다면, category로 바꾸면 메모리가 90% 이상 줄어들 수 있어요!
추가로 category 타입은 정렬 순서를 지정할 수 있어서, "브론즈 < 실버 < 골드 < 플래티넘" 같은 순서가 있는 데이터를 다룰 때도 유용합니다.
코드 예제
import pandas as pd
# 반복되는 값이 많은 데이터
data = {'이름': ['김철수', '이영희', '박민수', '최지우', '정민호'],
'성별': ['남성', '여성', '남성', '여성', '남성'],
'등급': ['골드', '실버', '골드', '브론즈', '플래티넘']}
df = pd.DataFrame(data)
# 메모리 사용량 확인 (변환 전)
print(f"변환 전: {df.memory_usage(deep=True).sum()} bytes")
# 카테고리 타입으로 변환
df['성별'] = df['성별'].astype('category')
df['등급'] = pd.Categorical(df['등급'],
categories=['브론즈', '실버', '골드', '플래티넘'], ordered=True)
# 메모리 사용량 확인 (변환 후)
print(f"변환 후: {df.memory_usage(deep=True).sum()} bytes")
print(df['등급'].sort_values())
설명
이것이 하는 일: 반복되는 문자열 데이터를 효율적인 category 타입으로 변환하여 메모리를 절약합니다. 첫 번째로, memory_usage(deep=True)로 변환 전 메모리 사용량을 측정했습니다.
deep=True를 넣어야 문자열의 실제 메모리까지 계산해줍니다. 그 다음으로, 두 가지 변환 방법을 보여드렸어요.
astype('category')는 간단한 변환용이고, pd.Categorical()은 순서까지 지정할 때 사용합니다. 세 번째로, 등급 컬럼에 순서를 지정했습니다.
categories 리스트의 순서대로 "브론즈 < 실버 < 골드 < 플래티넘"이 됩니다. 이제 sort_values()를 하면 이 순서대로 정렬됩니다.
여러분이 이 코드를 대용량 데이터에 적용하면 메모리 사용량이 50~90% 줄어들고, 필터링이나 그룹화 연산도 더 빨라집니다.
실전 팁
💡 고유값이 전체 데이터의 50% 미만일 때 category가 효과적입니다
💡 순서가 있는 데이터(등급, 학년 등)는 반드시 ordered=True로 설정하세요
💡 새로운 값을 추가하려면 먼저 cat.add_categories()로 카테고리를 추가해야 해요
💡 df.select_dtypes(include='object')로 문자열 컬럼만 찾아서 일괄 변환할 수 있습니다
4. Min-Max_정규화로_범위_맞추기
시작하며
여러분이 "나이"와 "연봉" 두 컬럼을 비교하려고 하는데, 나이는 2060 범위이고 연봉은 30009000만원 범위라면 어떨까요? 두 데이터를 같이 그래프로 그리거나 머신러닝에 넣으면 연봉이 나이를 완전히 압도해버립니다.
이런 문제를 "스케일 불균형"이라고 합니다. 머신러닝 알고리즘 대부분은 숫자의 크기에 민감해서, 큰 숫자가 있는 컬럼이 결과에 더 큰 영향을 미치게 됩니다.
바로 이럴 때 Min-Max 정규화가 필요합니다. 모든 데이터를 0과 1 사이로 맞춰서 공정하게 비교할 수 있게 해주거든요.
개요
간단히 말해서, Min-Max 정규화는 모든 학생의 점수를 100점 만점으로 환산하는 것과 같습니다. 수학 시험이 50점 만점이고 영어 시험이 100점 만점이라면, 그냥 더하면 안 되잖아요?
둘 다 100점 만점으로 바꿔야 공정하게 비교할 수 있습니다. 공식은 간단합니다: (값 - 최솟값) / (최댓값 - 최솟값).
이러면 가장 작은 값은 0이 되고, 가장 큰 값은 1이 됩니다. 예를 들어, 나이가 20~60 범위라면 20살은 0으로, 60살은 1로, 40살은 0.5로 변환됩니다.
연봉도 마찬가지로 0~1 사이로 변환되면 이제 두 데이터를 공정하게 비교할 수 있어요! Min-Max 정규화는 특히 신경망, KNN, SVM 같이 거리 기반 알고리즘에서 필수입니다.
코드 예제
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
# 스케일이 다른 데이터
data = {'나이': [25, 35, 45, 55, 30],
'연봉': [3500, 5000, 7000, 8500, 4000],
'근속년수': [1, 5, 15, 25, 3]}
df = pd.DataFrame(data)
# Min-Max 스케일러 생성 및 적용
scaler = MinMaxScaler()
df_scaled = pd.DataFrame(
scaler.fit_transform(df),
columns=df.columns
)
print("원본 데이터:")
print(df)
print("\n정규화된 데이터 (0~1 범위):")
print(df_scaled.round(2))
설명
이것이 하는 일: 서로 다른 범위의 숫자들을 모두 0과 1 사이로 맞춰서 공정하게 비교할 수 있게 합니다. 첫 번째로, 스케일이 완전히 다른 세 개의 컬럼을 준비했습니다.
나이는 2555, 연봉은 35008500, 근속년수는 1~25입니다. 이대로 머신러닝에 넣으면 연봉이 모든 것을 지배하게 됩니다.
그 다음으로, sklearn의 MinMaxScaler를 사용했습니다. fit_transform()은 "데이터를 분석하고(fit) 변환해줘(transform)"라는 뜻입니다.
한 번에 두 작업을 수행하는 편리한 메서드예요. 세 번째로, 결과를 DataFrame으로 다시 만들었습니다.
scaler가 반환하는 건 numpy 배열이라서, Pandas로 계속 작업하려면 DataFrame으로 감싸줘야 해요. 여러분이 이 코드를 사용하면 모든 컬럼이 0~1 범위로 맞춰져서 머신러닝 모델의 성능이 크게 향상됩니다.
실전 팁
💡 테스트 데이터에는 fit_transform() 대신 transform()만 사용하세요 (학습 데이터 기준 유지)
💡 이상치(outlier)가 있으면 Min-Max 정규화가 왜곡될 수 있어요
💡 정규화 후에도 원본 데이터는 따로 보관해두세요 (나중에 역변환 필요할 수 있음)
💡 feature_range=(0, 10) 처럼 다른 범위로도 변환할 수 있습니다
5. Z-Score_표준화로_정규분포_만들기
시작하며
Min-Max 정규화가 있는데 왜 또 다른 방법이 필요할까요? Min-Max는 이상치에 너무 민감하다는 단점이 있습니다.
데이터 중에 하나만 엄청 크면, 나머지가 다 0 근처로 몰려버리거든요. 예를 들어, 대부분의 연봉이 3000~5000만원인데 한 명만 10억이라면?
Min-Max를 적용하면 그 한 명만 1이 되고 나머지는 전부 0.005 같은 아주 작은 값이 되어버립니다. 바로 이럴 때 Z-Score 표준화가 해결책입니다.
평균을 0으로, 표준편차를 1로 만들어서 이상치의 영향을 줄여줍니다.
개요
간단히 말해서, Z-Score 표준화는 "평균에서 얼마나 떨어져 있나?"를 숫자로 표현하는 것입니다. Z=0이면 딱 평균이고, Z=1이면 평균보다 표준편차 1만큼 높은 거예요.
공식은: (값 - 평균) / 표준편차. 이렇게 하면 데이터가 평균 0, 표준편차 1인 분포로 변환됩니다.
Z-Score의 장점은 이상치가 있어도 나머지 데이터가 잘 분포된다는 점입니다. 10억 연봉자는 Z=10 정도의 극단적인 값이 되지만, 다른 데이터들은 여전히 -2~2 사이에 골고루 분포합니다.
특히 선형회귀, 로지스틱회귀, PCA 같은 알고리즘은 데이터가 정규분포를 따를 때 성능이 좋아서 Z-Score 표준화가 권장됩니다.
코드 예제
import pandas as pd
from sklearn.preprocessing import StandardScaler
# 이상치가 포함된 데이터
data = {'이름': ['A', 'B', 'C', 'D', 'E', '이상치'],
'점수': [75, 82, 78, 85, 80, 150]}
df = pd.DataFrame(data)
# Z-Score 표준화
scaler = StandardScaler()
df['점수_표준화'] = scaler.fit_transform(df[['점수']])
# 결과 확인
print(df)
print(f"\n평균: {df['점수_표준화'].mean():.2f}")
print(f"표준편차: {df['점수_표준화'].std():.2f}")
# 해석: Z-Score가 2 이상이면 이상치로 판단
outliers = df[abs(df['점수_표준화']) > 2]
print(f"\n이상치 후보: {outliers['이름'].values}")
설명
이것이 하는 일: 데이터를 평균 0, 표준편차 1인 분포로 변환하고, 이상치를 탐지하는 데도 활용합니다. 첫 번째로, 일부러 이상치(150점)를 포함한 데이터를 만들었습니다.
다른 점수들은 75~85점 사이인데 혼자 150점이니 확실한 이상치죠. 그 다음으로, StandardScaler를 적용했습니다.
MinMaxScaler와 사용법이 똑같아서 쉽게 바꿔 쓸 수 있어요. 결과를 보면 평균이 거의 0, 표준편차가 거의 1이 됩니다.
세 번째로, Z-Score의 유용한 활용법인 이상치 탐지를 보여드렸습니다. 일반적으로 Z-Score의 절댓값이 2~3을 넘으면 이상치로 판단합니다.
우리 데이터에서 150점은 Z=2.6 정도로 이상치로 탐지됩니다. 여러분이 이 코드를 사용하면 스케일 조정과 이상치 탐지를 동시에 할 수 있어서 데이터 전처리가 효율적이 됩니다.
실전 팁
💡 정규분포를 가정하는 알고리즘(회귀, PCA)에는 Z-Score가 더 적합해요
💡 Z-Score > 3 또는 < -3이면 거의 확실한 이상치입니다
💡 데이터가 정규분포가 아니면 로그 변환 후 표준화하는 것도 방법입니다
💡 StandardScaler도 학습/테스트 데이터를 나눠서 fit과 transform을 분리해야 해요
6. 불리언_타입_변환과_활용
시작하며
데이터에 "예/아니오", "Y/N", "true/false", "1/0" 같은 값들이 섞여 있는 경험이 있으신가요? 같은 의미인데 표현만 다른 거죠.
이런 데이터를 분석하려면 하나의 형식으로 통일해야 합니다. 또한 "이 고객이 VIP인가?", "이 상품이 품절인가?" 같은 질문에 대한 답은 결국 "예" 아니면 "아니오"입니다.
이런 데이터를 불리언(Boolean) 타입으로 저장하면 메모리도 절약되고 조건문 작성도 편해집니다. 바로 이럴 때 불리언 타입 변환이 필요합니다.
개요
간단히 말해서, 불리언은 "맞다/틀리다"를 표현하는 가장 단순한 타입입니다. True(참) 또는 False(거짓) 딱 두 가지 값만 가질 수 있어요.
왜 불리언이 좋을까요? 첫째, 메모리를 아낍니다.
"예"라는 문자열은 6바이트지만 True는 1바이트예요. 둘째, 조건문이 깔끔해집니다.
if row['VIP여부'] == '예' 대신 if row['is_vip']로 쓸 수 있죠. 실무에서는 다양한 형식의 예/아니오 데이터를 만나게 됩니다.
"Y", "Yes", "TRUE", "1", "참" 등등... 이걸 모두 True/False로 통일하는 작업이 필요합니다.
Pandas에서는 map() 함수나 replace()를 사용해서 쉽게 변환할 수 있고, 조건식을 직접 써서 불리언 컬럼을 만들 수도 있습니다.
코드 예제
import pandas as pd
# 다양한 형식의 예/아니오 데이터
data = {'이름': ['김철수', '이영희', '박민수', '최지우'],
'VIP': ['Y', 'N', 'Yes', 'No'],
'구매금액': [150000, 30000, 200000, 80000]}
df = pd.DataFrame(data)
# 문자열을 불리언으로 변환 (여러 형식 처리)
yes_values = ['Y', 'Yes', 'yes', 'TRUE', 'true', '1', '예']
df['is_vip'] = df['VIP'].isin(yes_values)
# 조건으로 불리언 컬럼 생성
df['고액구매자'] = df['구매금액'] >= 100000
print(df)
print(f"\nVIP 고객 수: {df['is_vip'].sum()}")
print(f"고액구매자 비율: {df['고액구매자'].mean() * 100:.1f}%")
설명
이것이 하는 일: 다양한 형식의 예/아니오 데이터를 True/False로 통일하고, 조건에 따른 불리언 컬럼을 생성합니다. 첫 번째로, 일부러 'Y', 'N', 'Yes', 'No'가 섞인 데이터를 만들었습니다.
실제로 여러 소스에서 데이터를 합치면 이런 일이 자주 발생해요. 그 다음으로, isin() 함수를 사용했습니다.
yes_values 리스트에 "예"를 의미하는 모든 값을 넣어두고, 해당하면 True, 아니면 False가 됩니다. 정말 편리하죠?
세 번째로, 조건식으로 새 불리언 컬럼을 만들었습니다. df['구매금액'] >= 100000 자체가 True/False 시리즈를 반환하기 때문에 바로 새 컬럼에 할당할 수 있어요.
여러분이 이 코드를 사용하면 복잡한 조건을 깔끔한 불리언 컬럼으로 저장하고, sum()으로 개수를, mean()으로 비율을 바로 계산할 수 있습니다.
실전 팁
💡 불리언 컬럼 이름은 is_, has_, can_ 등의 접두사를 붙이면 의미가 명확해져요
💡 True는 1, False는 0으로 자동 변환되어서 sum()하면 True 개수가 나옵니다
💡 여러 불리언 조건을 &(AND), |(OR)로 조합할 수 있어요
💡 NaN이 포함된 경우 fillna(False)로 먼저 처리하세요
7. 로그_변환으로_왜곡된_분포_바로잡기
시작하며
데이터의 히스토그램을 그렸는데, 대부분이 왼쪽에 몰려있고 오른쪽으로 긴 꼬리가 늘어진 모양을 본 적 있나요? 이런 분포를 "오른쪽 꼬리 분포" 또는 "양의 왜도"라고 합니다.
연봉, 부동산 가격, 웹사이트 방문 수, 상품 판매량 등 현실의 많은 데이터가 이런 모양입니다. 대부분은 적당한 값이고, 일부만 엄청 큰 값을 가지죠.
이런 데이터를 그대로 머신러닝에 넣으면 성능이 떨어집니다. 바로 이럴 때 로그 변환이 마법처럼 분포를 정규분포에 가깝게 만들어줍니다.
개요
간단히 말해서, 로그 변환은 큰 값을 확 줄이고 작은 값의 차이를 부각시키는 변환입니다. 로그의 특성상 값이 클수록 줄어드는 폭이 커서, 꼬리가 긴 분포를 대칭적인 종 모양으로 바꿔줍니다.
예를 들어볼까요? log(10) = 1, log(100) = 2, log(1000) = 3입니다.
원래 10배씩 차이나던 것이 로그 변환 후에는 1씩만 차이나게 됩니다. 극단적인 값의 영향력이 크게 줄어드는 거죠.
실무에서 로그 변환은 정말 자주 사용됩니다. 금융 데이터, 인구 통계, 웹 분석 등 "큰 숫자가 가끔 나타나는" 모든 분야에서 필수 기법이에요.
주의할 점은 0이나 음수에는 로그를 적용할 수 없다는 것입니다. 그래서 보통 log(x + 1) 형태로 1을 더해서 사용합니다.
코드 예제
import pandas as pd
import numpy as np
# 오른쪽으로 치우친 데이터 (소득 분포 예시)
data = {'이름': ['A', 'B', 'C', 'D', 'E', 'F', '부자'],
'월소득': [250, 300, 280, 320, 350, 400, 5000]}
df = pd.DataFrame(data)
# 로그 변환 적용 (log1p = log(x + 1))
df['월소득_로그'] = np.log1p(df['월소득'])
# 원본과 변환 후 통계 비교
print("원본 데이터 통계:")
print(df['월소득'].describe())
print(f"\n로그 변환 후 통계:")
print(df['월소득_로그'].describe())
# 역변환도 가능 (expm1 = exp(x) - 1)
df['복원'] = np.expm1(df['월소득_로그'])
print(f"\n역변환 확인: {df['복원'].tolist()}")
설명
이것이 하는 일: 극단적으로 큰 값이 있는 데이터의 분포를 정규분포에 가깝게 변환합니다. 첫 번째로, 일부러 극단적인 분포의 데이터를 만들었습니다.
대부분은 250~400 사이인데 한 명만 5000입니다. 실제 소득 분포와 비슷하죠?
그 다음으로, np.log1p() 함수를 사용했습니다. 이건 log(x + 1)을 계산하는 함수예요.
왜 1을 더할까요? 만약 월소득이 0인 사람이 있으면 log(0)은 계산이 안 되거든요.
1을 더하면 안전합니다. 세 번째로, np.expm1()으로 역변환을 보여드렸습니다.
로그 변환된 데이터에서 원래 값을 복원할 수 있어요. 모델 예측 후 결과를 사람이 이해할 수 있는 값으로 바꿀 때 필요합니다.
여러분이 이 코드를 사용하면 치우친 분포의 데이터도 머신러닝 모델이 잘 학습할 수 있게 됩니다.
실전 팁
💡 히스토그램을 그려서 오른쪽 꼬리가 길면 로그 변환을 고려하세요
💡 로그 변환 후에도 치우쳐 있으면 제곱근 변환(np.sqrt)을 시도해보세요
💡 음수가 포함된 데이터는 로그 변환 전에 최솟값을 더해서 양수로 만들어야 해요
💡 결과 해석 시 역변환을 잊지 마세요! 로그 스케일의 숫자는 직관적이지 않습니다
8. 원-핫_인코딩으로_범주형_데이터_변환하기
시작하며
머신러닝 모델에 "서울", "부산", "대구" 같은 문자열을 넣으면 어떻게 될까요? 에러가 납니다.
대부분의 머신러닝 알고리즘은 숫자만 이해하거든요. 그렇다면 서울=1, 부산=2, 대구=3으로 바꾸면 될까요?
이것도 문제가 있어요. 모델이 "대구(3)는 서울(1)보다 3배 크다"라고 오해할 수 있거든요.
지역에는 그런 순서나 크기가 없잖아요. 바로 이럴 때 원-핫 인코딩이 필요합니다.
각 카테고리를 별개의 컬럼으로 만들어서 모델의 오해를 방지해줍니다.
개요
간단히 말해서, 원-핫 인코딩은 "해당하면 1, 아니면 0"으로 표시하는 방법입니다. 서울, 부산, 대구가 있으면 세 개의 컬럼을 만들어서 서울 사람은 [1, 0, 0], 부산 사람은 [0, 1, 0]으로 표시하는 거예요.
왜 이 방법이 좋을까요? 모델이 각 지역을 독립적인 특성으로 인식합니다.
서울과 부산 사이에 수학적 관계가 없다는 것을 정확히 반영하는 거죠. 예를 들어, 고객의 선호 음식이 "한식", "중식", "양식" 중 하나라면, 원-핫 인코딩 후에는 세 개의 컬럼이 생기고 각각의 영향력을 개별적으로 학습할 수 있게 됩니다.
단점은 카테고리가 많으면 컬럼 수가 폭발한다는 것입니다. 1000개의 동네가 있으면 1000개의 컬럼이 생겨요.
이럴 때는 다른 인코딩 방법을 고려해야 합니다.
코드 예제
import pandas as pd
# 범주형 데이터
data = {'이름': ['김철수', '이영희', '박민수', '최지우'],
'지역': ['서울', '부산', '서울', '대구'],
'선호음식': ['한식', '양식', '중식', '한식']}
df = pd.DataFrame(data)
# 원-핫 인코딩 적용
df_encoded = pd.get_dummies(df, columns=['지역', '선호음식'],
prefix=['지역', '음식'])
print("원본:")
print(df)
print("\n원-핫 인코딩 후:")
print(df_encoded)
# 특정 컬럼만 확인
print(f"\n생성된 컬럼들: {[c for c in df_encoded.columns if '지역' in c or '음식' in c]}")
설명
이것이 하는 일: 문자열 범주 데이터를 머신러닝이 이해할 수 있는 숫자 형태로 변환합니다. 첫 번째로, 두 개의 범주형 컬럼(지역, 선호음식)이 있는 데이터를 준비했습니다.
이 컬럼들은 문자열이라 그대로는 모델에 넣을 수 없어요. 그 다음으로, pd.get_dummies()를 사용했습니다.
columns 파라미터로 변환할 컬럼을 지정하고, prefix로 새 컬럼 이름 앞에 붙일 접두사를 정했습니다. "지역_서울", "지역_부산" 이런 식으로 이름이 지어집니다.
세 번째로, 결과를 확인해보면 '이름' 컬럼은 그대로 있고, '지역'과 '선호음식'은 사라지고 대신 여러 개의 0/1 컬럼이 생겼습니다. 여러분이 이 코드를 사용하면 어떤 범주형 데이터든 머신러닝 모델에 바로 넣을 수 있는 형태로 바꿀 수 있습니다.
실전 팁
💡 카테고리가 너무 많으면(50개 이상) 타겟 인코딩이나 임베딩을 고려하세요
💡 drop_first=True 옵션을 쓰면 다중공선성을 방지할 수 있어요
💡 테스트 데이터에 새로운 카테고리가 있으면 에러가 나니 주의하세요
💡 sklearn의 OneHotEncoder는 학습/테스트 분리 상황에서 더 안전합니다
9. 결측값_처리_전략
시작하며
데이터를 불러왔는데 NaN, null, 빈 칸이 군데군데 보이는 경험, 다들 있으시죠? 이게 바로 결측값(Missing Value)입니다.
결측값이 있으면 계산도 안 되고, 모델 학습도 제대로 안 됩니다. 결측값은 왜 생길까요?
설문조사에서 응답 안 한 항목, 센서 오류로 기록 안 된 데이터, 시스템 버그로 저장 안 된 값 등 원인은 다양합니다. 바로 이럴 때 결측값 처리 전략이 필요합니다.
무작정 삭제하면 소중한 데이터를 잃고, 잘못 채우면 분석 결과가 왜곡됩니다. 상황에 맞는 처리가 중요해요.
개요
간단히 말해서, 결측값 처리는 빈 칸을 어떻게 할지 결정하는 것입니다. 크게 세 가지 방법이 있어요: 삭제하기, 채우기, 예측하기.
삭제는 가장 간단하지만 데이터 손실이 있습니다. 결측값이 5% 미만이고 무작위로 발생했다면 삭제해도 괜찮아요.
채우기는 평균, 중앙값, 최빈값 등으로 빈 칸을 메우는 방법입니다. 숫자 데이터는 평균이나 중앙값으로, 범주형 데이터는 최빈값으로 채우는 게 일반적이에요.
고급 방법으로는 KNN이나 회귀 모델로 다른 컬럼을 기반으로 결측값을 예측하는 방법도 있습니다. 결측값이 많거나 중요한 컬럼일 때 사용해요.
코드 예제
import pandas as pd
import numpy as np
# 결측값이 있는 데이터
data = {'이름': ['A', 'B', 'C', 'D', 'E'],
'나이': [25, np.nan, 35, 28, np.nan],
'성별': ['남', '여', np.nan, '남', '여'],
'점수': [85, 90, np.nan, 75, 88]}
df = pd.DataFrame(data)
print("원본 (결측값 확인):")
print(df.isnull().sum())
# 숫자형: 중앙값으로 채우기
df['나이'] = df['나이'].fillna(df['나이'].median())
# 범주형: 최빈값으로 채우기
df['성별'] = df['성별'].fillna(df['성별'].mode()[0])
# 특정 값으로 채우기 + 결측 여부 표시
df['점수_결측'] = df['점수'].isnull()
df['점수'] = df['점수'].fillna(df['점수'].mean())
print("\n처리 후:")
print(df)
설명
이것이 하는 일: 데이터의 빈 칸을 적절한 값으로 채워서 분석 가능한 상태로 만듭니다. 첫 번째로, isnull().sum()으로 각 컬럼의 결측값 개수를 확인했습니다.
어디에 얼마나 결측값이 있는지 파악하는 게 첫 단계예요. 그 다음으로, 나이(숫자)는 중앙값으로 채웠습니다.
왜 평균이 아니라 중앙값일까요? 이상치가 있으면 평균이 왜곡되기 때문이에요.
중앙값이 더 안정적입니다. 세 번째로, 성별(범주형)은 최빈값(가장 많이 나온 값)으로 채웠습니다.
mode()[0]에서 [0]을 붙이는 이유는 최빈값이 여러 개일 수 있어서 첫 번째를 선택하는 거예요. 마지막으로, 점수 컬럼은 결측 여부를 별도 컬럼으로 저장했습니다.
"이 값이 원래 비어있었다"는 정보가 모델에 유용할 수 있거든요.
실전 팁
💡 결측값을 채우기 전에 왜 결측이 발생했는지 이유를 파악하세요
💡 결측값이 30% 이상인 컬럼은 삭제를 고려하세요
💡 시계열 데이터는 앞뒤 값의 평균(interpolate)으로 채우는 게 좋아요
💡 결측 여부 자체를 특성으로 사용하면 모델 성능이 올라갈 수 있습니다
💡 sklearn의 SimpleImputer를 쓰면 파이프라인에 결측값 처리를 포함시킬 수 있어요
10. 이상치_탐지와_처리
시작하며
데이터에서 "말이 안 되는" 값을 본 적 있나요? 나이가 200살이라거나, 키가 마이너스라거나, 월급이 0원인데 연봉이 1억이라거나.
이런 값들이 이상치(Outlier)입니다. 이상치는 두 종류가 있어요.
진짜 에러인 경우(잘못 입력된 데이터)와 진짜 특이한 경우(정말로 그런 사람이 있는 경우). 어떤 경우인지에 따라 처리 방법이 달라집니다.
바로 이럴 때 이상치 탐지 기법이 필요합니다. 눈으로 다 볼 수 없는 대용량 데이터에서 이상한 값을 자동으로 찾아내야 하거든요.
개요
간단히 말해서, 이상치 탐지는 "다른 애들과 너무 다른 값 찾기"입니다. 대표적인 방법으로 IQR(사분위수 범위)과 Z-Score가 있어요.
IQR 방법은 상자그림(Box Plot)에서 쓰는 방식입니다. Q1(25%) - 1.5×IQR보다 작거나 Q3(75%) + 1.5×IQR보다 크면 이상치로 판단해요.
Z-Score 방법은 앞서 배웠죠? 평균에서 표준편차의 3배 이상 떨어지면 이상치로 봅니다.
이상치를 찾았으면 어떻게 처리할까요? 삭제, 대체(경계값으로 바꾸기), 또는 그대로 두기 중에서 선택합니다.
데이터의 특성과 분석 목적에 따라 다르게 처리해야 해요.
코드 예제
import pandas as pd
import numpy as np
# 이상치가 포함된 데이터
data = {'이름': ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
'점수': [75, 82, 78, 85, 80, 200, 77]} # 200은 이상치
df = pd.DataFrame(data)
# IQR 방법으로 이상치 탐지
Q1 = df['점수'].quantile(0.25)
Q3 = df['점수'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
# 이상치 표시
df['이상치'] = (df['점수'] < lower) | (df['점수'] > upper)
print(f"이상치 범위: {lower:.1f} ~ {upper:.1f}")
print(df)
# 이상치 처리: 경계값으로 대체 (Capping/Winsorizing)
df['점수_처리'] = df['점수'].clip(lower=lower, upper=upper)
print(f"\n처리 후: {df['점수_처리'].tolist()}")
설명
이것이 하는 일: 데이터에서 통계적으로 비정상적인 값을 찾아내고 적절히 처리합니다. 첫 번째로, quantile()로 Q1(25번째 백분위수)과 Q3(75번째 백분위수)를 구했습니다.
IQR은 Q3에서 Q1을 뺀 값으로, 데이터가 퍼진 정도를 나타냅니다. 그 다음으로, 이상치 판단 기준인 lower와 upper를 계산했습니다.
Q1 - 1.5×IQR보다 작거나 Q3 + 1.5×IQR보다 크면 이상치예요. 1.5라는 숫자는 통계학에서 오랫동안 사용되어 온 기준입니다.
세 번째로, clip() 함수로 이상치를 처리했습니다. 이 방법은 "경계값으로 깎기"라고 해서, 이상치를 삭제하지 않고 허용 범위 내의 값으로 바꿉니다.
200점이 upper 경계값으로 바뀌는 거죠. 여러분이 이 코드를 사용하면 데이터의 이상치를 자동으로 탐지하고 적절히 처리할 수 있습니다.
실전 팁
💡 이상치를 무조건 삭제하지 마세요! 진짜 의미 있는 데이터일 수 있어요
💡 도메인 지식을 활용하세요 (나이 이상치 기준은 0120, 확률은 01 등)
💡 시각화(Box Plot, Scatter Plot)로 먼저 눈으로 확인하세요
💡 이상치 탐지 전에 로그 변환을 하면 더 정확할 수 있어요
💡 머신러닝에서는 이상치에 강건한 알고리즘(랜덤 포레스트 등)을 선택하는 것도 방법입니다
댓글 (0)
함께 보면 좋은 카드 뉴스
범주형 변수 시각화 완벽 가이드 Bar Chart와 Count Plot
데이터 분석에서 가장 기본이 되는 범주형 변수 시각화 방법을 알아봅니다. Matplotlib의 Bar Chart부터 Seaborn의 Count Plot까지, 실무에서 바로 활용할 수 있는 시각화 기법을 배워봅니다.
이변량 분석 완벽 가이드: 변수 간 관계 탐색
두 변수 사이의 관계를 분석하는 이변량 분석의 핵심 개념과 기법을 배웁니다. 상관관계, 산점도, 교차분석 등 데이터 분석의 필수 도구들을 실습과 함께 익혀봅시다.
단변량 분석 분포 시각화 완벽 가이드
데이터 분석의 첫걸음인 단변량 분석과 분포 시각화를 배웁니다. 히스토그램, 박스플롯, 밀도 그래프 등 다양한 시각화 방법을 초보자도 쉽게 이해할 수 있도록 설명합니다.
이상치 탐지 및 처리 완벽 가이드
데이터 속에 숨어있는 이상한 값들을 찾아내고 처리하는 방법을 배워봅니다. 실무에서 자주 마주치는 이상치 문제를 Python으로 해결하는 다양한 기법을 소개합니다.
결측치 처리 전략 완벽 가이드
데이터 분석에서 가장 먼저 만나는 문제, 결측치! 삭제부터 고급 대체 기법까지, 실무에서 바로 쓸 수 있는 결측치 처리 전략을 초급자도 이해하기 쉽게 알려드립니다. 데이터의 품질을 높이고 정확한 분석 결과를 얻어보세요.