이미지 로딩 중...
AI Generated
2025. 11. 21. · 5 Views
Pandas 데이터 전처리 마스터
실무에서 가장 많이 사용되는 Pandas 데이터 전처리 기법을 배웁니다. 누락 데이터 처리부터 데이터 변환까지, 데이터 분석의 핵심 단계를 완벽하게 마스터해보세요.
목차
1. 누락_데이터_처리
시작하며
여러분이 고객 데이터를 분석하려고 엑셀 파일을 열었는데, 곳곳에 빈 칸이 보이는 상황을 겪어본 적 있나요? 전화번호 칸이 비어있거나, 나이 정보가 없거나, 주소가 빠져있는 경우처럼 말이죠.
이런 누락 데이터는 실제 데이터 분석에서 가장 흔하게 마주치는 문제입니다. 설문조사에서 응답자가 일부 질문을 건너뛰거나, 시스템 오류로 데이터가 저장되지 않거나, 수동 입력 과정에서 실수가 발생할 수 있습니다.
이런 빈 데이터를 그대로 두면 분석 결과가 왜곡되고, 심지어 프로그램이 에러를 일으킬 수도 있습니다. 바로 이럴 때 필요한 것이 누락 데이터 처리 기법입니다.
Pandas는 이런 빈 칸들을 찾아내고, 채우거나 제거하는 다양한 방법을 제공합니다.
개요
간단히 말해서, 누락 데이터 처리는 데이터셋에서 비어있는 값들을 찾아서 적절하게 관리하는 과정입니다. 왜 이 과정이 필요할까요?
대부분의 머신러닝 알고리즘은 빈 값을 처리하지 못합니다. 마치 빈 칸이 있는 수학 문제를 풀 수 없는 것처럼요.
예를 들어, 100명의 고객 데이터 중 20명의 나이 정보가 없다면, 나이별 구매 패턴을 분석할 수 없게 됩니다. 기존에는 엑셀에서 일일이 찾아서 손으로 수정했다면, 이제는 Pandas로 자동화할 수 있습니다.
Pandas는 세 가지 주요 방법을 제공합니다: (1) 누락 데이터가 있는 행 전체를 삭제하기, (2) 평균값이나 중앙값으로 채우기, (3) 앞뒤 값으로 채우기. 이러한 방법들을 상황에 맞게 선택하면 데이터 품질을 크게 향상시킬 수 있습니다.
코드 예제
import pandas as pd
import numpy as np
# 샘플 데이터 생성 (빈 값 포함)
df = pd.DataFrame({
'name': ['철수', '영희', '민수', '지영'],
'age': [25, np.nan, 30, 28],
'score': [85, 90, np.nan, 88]
})
# 누락 데이터 확인
print(df.isnull().sum()) # 각 열의 누락 개수
# 방법 1: 누락 데이터가 있는 행 삭제
df_dropped = df.dropna()
# 방법 2: 평균값으로 채우기
df['age'].fillna(df['age'].mean(), inplace=True)
# 방법 3: 특정 값으로 채우기
df['score'].fillna(0, inplace=True)
설명
이것이 하는 일: 데이터에서 비어있는 값(NaN)을 찾아내고, 삭제하거나 적절한 값으로 대체하여 분석 가능한 상태로 만듭니다. 첫 번째로, isnull().sum()은 각 열에 빈 값이 몇 개 있는지 세어줍니다.
마치 시험지에서 빈 칸이 몇 개인지 확인하는 것과 같습니다. 이를 통해 어느 열에 문제가 있는지 한눈에 파악할 수 있습니다.
두 번째로, dropna()는 빈 값이 하나라도 있는 행을 통째로 지워버립니다. 4명의 데이터 중 2명에게 빈 값이 있다면, 결과적으로 2명의 완전한 데이터만 남게 됩니다.
이 방법은 데이터가 충분히 많고, 누락 비율이 낮을 때 유용합니다. 세 번째로, fillna()는 빈 칸을 특정 값으로 채워줍니다.
age 열의 빈 값을 전체 나이의 평균으로 채우면, 데이터를 버리지 않고도 분석을 진행할 수 있습니다. score 열은 0으로 채웠는데, 이는 시험을 보지 않은 경우를 0점으로 처리하는 것과 같습니다.
여러분이 이 코드를 사용하면 수천, 수만 건의 데이터에서 누락 값을 자동으로 처리할 수 있습니다. 수작업으로 하루가 걸릴 일을 몇 초 만에 끝낼 수 있고, 실수 없이 일관된 방식으로 처리할 수 있으며, 언제든 다른 방법으로 쉽게 바꿔볼 수 있습니다.
실전 팁
💡 inplace=True 옵션을 사용하면 원본 데이터프레임을 직접 수정하므로, 원본을 보존하고 싶다면 먼저 복사본을 만드세요 (df.copy())
💡 평균값으로 채울 때는 이상치(극단적으로 크거나 작은 값)가 있는지 먼저 확인하세요. 이상치가 있으면 평균 대신 중앙값(median)을 사용하는 게 더 안전합니다
💡 dropna(thresh=n)을 사용하면 최소 n개의 값이 있는 행만 유지할 수 있습니다. 예를 들어 thresh=3이면 3개 이상의 값이 있는 행만 남깁니다
💡 특정 열만 기준으로 삭제하려면 dropna(subset=['age'])처럼 subset 파라미터를 활용하세요
💡 시계열 데이터에서는 fillna(method='ffill')로 앞의 값을 복사하거나 fillna(method='bfill')로 뒤의 값을 복사하는 방법이 효과적입니다
2. 중복_데이터_제거
시작하며
여러분이 고객 명단을 정리하다가 같은 사람이 여러 번 등록되어 있는 걸 발견한 적 있나요? 이메일이 똑같은데 이름 표기만 조금 다르거나, 아예 모든 정보가 똑같이 중복된 경우처럼 말이죠.
이런 중복 데이터는 데이터베이스 통합, 여러 소스에서 데이터 수집, 또는 사용자의 실수로 자주 발생합니다. 중복된 데이터를 그대로 두면 분석 결과가 왜곡됩니다.
예를 들어, 같은 고객이 3번 카운트되면 실제보다 고객 수가 부풀려지고, 평균 구매액도 잘못 계산됩니다. 바로 이럴 때 필요한 것이 중복 제거 기능입니다.
Pandas는 중복된 행을 쉽게 찾아내고 제거할 수 있는 강력한 도구를 제공합니다.
개요
간단히 말해서, 중복 데이터 제거는 데이터셋에서 똑같은 정보가 반복되는 행들을 찾아서 하나만 남기고 나머지를 삭제하는 과정입니다. 왜 이 작업이 중요할까요?
중복 데이터는 통계 분석을 완전히 망칠 수 있습니다. 100명의 고객 데이터라고 생각했는데 실제로는 80명이고 20명이 중복이었다면, 모든 분석 결과를 믿을 수 없게 됩니다.
예를 들어, 월별 가입자 수를 집계할 때 중복이 포함되면 성장률이 실제보다 높게 나타나는 착시 효과가 발생합니다. 기존에는 엑셀의 조건부 서식으로 중복을 찾고 수동으로 삭제했다면, 이제는 한 줄의 코드로 자동화할 수 있습니다.
Pandas의 중복 제거는 세 가지 유연성을 제공합니다: (1) 모든 열이 같은 완전 중복 찾기, (2) 특정 열만 기준으로 중복 찾기, (3) 첫 번째 또는 마지막 중복을 남길지 선택하기. 이러한 옵션들로 다양한 실무 상황에 대응할 수 있습니다.
코드 예제
import pandas as pd
# 중복이 포함된 데이터
df = pd.DataFrame({
'user_id': [1, 2, 2, 3, 4, 4],
'name': ['김철수', '이영희', '이영희', '박민수', '정지영', '정지영'],
'email': ['kim@email.com', 'lee@email.com', 'lee@email.com',
'park@email.com', 'jung@email.com', 'jung@email.com']
})
# 중복 확인
print(df.duplicated().sum()) # 중복된 행의 개수
# 방법 1: 완전히 동일한 행 제거 (첫 번째 유지)
df_no_dup = df.drop_duplicates()
# 방법 2: 특정 열 기준으로 중복 제거
df_unique_email = df.drop_duplicates(subset=['email'], keep='first')
# 방법 3: 마지막 중복을 유지
df_keep_last = df.drop_duplicates(keep='last')
설명
이것이 하는 일: 데이터프레임에서 동일한 정보를 가진 행들을 찾아내고, 설정에 따라 하나만 남기고 나머지를 삭제하여 고유한 데이터만 유지합니다. 첫 번째로, duplicated()는 각 행이 중복인지 아닌지를 True/False로 알려줍니다.
마치 출석부에서 같은 이름이 있는지 체크하는 것과 같습니다. sum()을 붙이면 중복이 총 몇 개인지 숫자로 보여주므로, 문제의 규모를 파악할 수 있습니다.
두 번째로, drop_duplicates()는 기본적으로 모든 열의 값이 완전히 똑같은 행을 중복으로 판단합니다. 6개 행 중에서 완전히 동일한 2개를 찾아내면, 첫 번째로 나타난 행만 남기고 나중에 나온 중복들을 삭제합니다.
결과적으로 고유한 데이터만 남게 됩니다. 세 번째로, subset=['email'] 옵션을 사용하면 이메일 열만 기준으로 중복을 판단합니다.
다른 정보는 달라도 이메일이 같으면 같은 사람으로 간주하는 것이죠. keep='last'를 쓰면 첫 번째 대신 마지막 중복을 유지하는데, 이는 최신 정보를 남기고 싶을 때 유용합니다.
여러분이 이 기능을 활용하면 수만 건의 데이터에서 중복을 자동으로 정리할 수 있습니다. 데이터 품질이 향상되어 정확한 분석이 가능해지고, 저장 공간도 절약되며, 처리 속도도 빨라집니다.
실전 팁
💡 중복을 제거하기 전에 먼저 duplicated(keep=False)로 중복된 모든 행을 확인해보세요. 실수로 중요한 데이터를 삭제하는 걸 방지할 수 있습니다
💡 여러 열을 기준으로 하려면 subset=['email', 'phone']처럼 리스트로 지정하세요. 이메일과 전화번호가 모두 같을 때만 중복으로 판단합니다
💡 대소문자 차이를 무시하려면 먼저 str.lower()로 소문자로 통일한 후 중복 제거를 하세요
💡 중복 제거 후 인덱스를 초기화하려면 reset_index(drop=True)를 연결해서 사용하세요
💡 중복 데이터를 삭제하지 않고 별도로 저장하고 싶다면 df[df.duplicated()]로 중복만 따로 추출할 수 있습니다
3. 데이터_타입_변환
시작하며
여러분이 숫자로 계산을 하려고 했는데 "숫자를 더할 수 없습니다"라는 에러가 뜨는 경험을 해본 적 있나요? 데이터를 열어보니 '25'처럼 따옴표로 묶인 문자열로 저장되어 있어서 계산이 안 되는 경우처럼 말이죠.
이런 데이터 타입 문제는 CSV 파일 불러오기, 웹 크롤링, API 데이터 수집 등에서 매우 흔하게 발생합니다. Pandas가 자동으로 타입을 추측하지만, 항상 정확한 건 아닙니다.
'2024-01-01'이 날짜가 아니라 문자열로 읽히거나, '1,234'가 숫자가 아니라 문자로 인식되는 경우가 많습니다. 바로 이럴 때 필요한 것이 데이터 타입 변환입니다.
올바른 타입으로 변환하면 계산도 가능하고, 날짜 연산도 할 수 있으며, 메모리도 효율적으로 사용할 수 있습니다.
개요
간단히 말해서, 데이터 타입 변환은 문자열, 숫자, 날짜 등 각 데이터를 올바른 형식으로 바꾸는 작업입니다. 왜 이 작업이 필요할까요?
컴퓨터는 '25'와 25를 완전히 다르게 취급합니다. 전자는 문자열이고 후자는 숫자입니다.
문자열로 저장된 나이 데이터로는 평균을 구할 수 없고, 날짜 형식이 아닌 문자열로는 "3개월 후"를 계산할 수 없습니다. 예를 들어, 판매 데이터의 금액이 문자열로 되어 있으면 총합을 구하거나 차트를 그릴 수 없습니다.
기존에는 엑셀에서 셀 서식을 일일이 바꿨다면, 이제는 Pandas로 전체 열을 한 번에 변환할 수 있습니다. Pandas는 다양한 변환 함수를 제공합니다: (1) astype()으로 기본 타입 변환, (2) to_numeric()으로 숫자 변환 시 에러 처리, (3) to_datetime()으로 날짜 형식 변환, (4) to_categorical()로 카테고리 타입 변환.
각 함수는 다양한 옵션으로 실무의 복잡한 상황을 처리할 수 있습니다.
코드 예제
import pandas as pd
# 잘못된 타입으로 저장된 데이터
df = pd.DataFrame({
'age': ['25', '30', 'unknown', '28'],
'price': ['1,234', '2,567', '3,890', '1,000'],
'date': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04'],
'category': ['A', 'B', 'A', 'C']
})
# 숫자로 변환 (에러는 NaN으로)
df['age'] = pd.to_numeric(df['age'], errors='coerce')
# 쉼표 제거 후 숫자로 변환
df['price'] = df['price'].str.replace(',', '').astype(int)
# 날짜 타입으로 변환
df['date'] = pd.to_datetime(df['date'])
# 카테고리 타입으로 변환 (메모리 절약)
df['category'] = df['category'].astype('category')
설명
이것이 하는 일: 데이터의 각 열을 분석 목적에 맞는 올바른 형식(정수, 실수, 날짜, 카테고리 등)으로 변환하여 계산과 분석이 가능하도록 만듭니다. 첫 번째로, to_numeric()은 문자열로 된 숫자를 실제 숫자 타입으로 바꿉니다.
'unknown'처럼 숫자가 아닌 값은 errors='coerce' 옵션 덕분에 에러를 내지 않고 NaN(빈 값)으로 변환됩니다. 이렇게 하면 일부 잘못된 데이터 때문에 전체 변환이 실패하는 걸 막을 수 있습니다.
두 번째로, price 열은 쉼표 때문에 바로 숫자로 변환할 수 없습니다. 그래서 먼저 str.replace(',', '')로 쉼표를 제거한 뒤, astype(int)로 정수형으로 변환합니다.
이제 이 데이터로 합계, 평균 등을 계산할 수 있습니다. 세 번째로, to_datetime()은 문자열로 된 날짜를 Pandas의 datetime 타입으로 바꿉니다.
이렇게 변환하면 날짜 간 차이를 계산하거나, 월별로 그룹화하거나, 요일을 추출하는 등 다양한 시계열 분석이 가능해집니다. 네 번째로, category 타입은 'A', 'B', 'C'처럼 반복되는 값을 효율적으로 저장합니다.
문자열로 저장하면 각 행마다 'A'를 반복 저장하지만, 카테고리로 변환하면 'A'를 한 번만 저장하고 참조만 하므로 메모리를 크게 절약할 수 있습니다. 여러분이 이 기능들을 사용하면 데이터 분석이 훨씬 쉬워집니다.
숫자 계산이 가능해지고, 날짜 연산을 자유롭게 할 수 있으며, 메모리 사용량도 최적화되고, 데이터 타입 에러로 인한 버그를 사전에 방지할 수 있습니다.
실전 팁
💡 변환 전에 항상 df.dtypes로 현재 데이터 타입을 먼저 확인하세요. 어떤 열을 변환해야 하는지 파악할 수 있습니다
💡 to_numeric()의 errors='ignore' 옵션은 변환 실패 시 원본을 그대로 유지하고, errors='raise'는 에러를 발생시킵니다. 상황에 맞게 선택하세요
💡 날짜 형식이 다양하다면 to_datetime(format='%Y-%m-%d')로 형식을 명시하면 더 빠르고 정확합니다
💡 astype() 사용 시 ValueError가 나면 변환할 수 없는 값이 있다는 뜻입니다. 먼저 해당 값들을 확인하고 처리하세요
💡 대용량 데이터에서는 int64 대신 int32, float64 대신 float32를 사용하면 메모리를 절반으로 줄일 수 있습니다
4. 문자열_데이터_정제
시작하며
여러분이 고객 이름을 정리하는데 앞뒤로 불필요한 공백이 있거나, 대소문자가 뒤섞여 있거나, 이메일 주소에서 도메인만 추출해야 하는 상황을 겪어본 적 있나요? 이런 문자열 정제 작업은 실무에서 정말 자주 발생합니다.
사용자가 입력한 데이터는 오타, 불필요한 공백, 일관되지 않은 대소문자 등 문제가 많습니다. " 김철수 "와 "김철수"는 사람 눈에는 같아 보이지만 컴퓨터는 다른 것으로 인식합니다.
이런 데이터로 중복 제거나 그룹화를 하면 결과가 잘못 나옵니다. 바로 이럴 때 필요한 것이 문자열 데이터 정제입니다.
Pandas의 str 접근자를 사용하면 파이썬 문자열 메서드를 전체 열에 한 번에 적용할 수 있습니다.
개요
간단히 말해서, 문자열 데이터 정제는 텍스트 데이터의 공백을 제거하고, 대소문자를 통일하고, 불필요한 문자를 없애고, 필요한 부분만 추출하는 과정입니다. 왜 이 작업이 필수일까요?
깨끗하지 않은 문자열 데이터는 분석을 방해합니다. 예를 들어, 같은 회사 이름이 "Apple Inc.", "apple inc", " Apple Inc "처럼 다양하게 입력되어 있으면, 그룹화했을 때 3개의 다른 회사로 카운트됩니다.
이메일 도메인별로 통계를 내려면 "user@gmail.com"에서 "gmail.com" 부분만 추출해야 합니다. 기존에는 엑셀의 TRIM, LOWER, UPPER 함수를 조합해서 썼다면, 이제는 Pandas의 str 메서드로 더 강력하게 처리할 수 있습니다.
Pandas의 문자열 메서드는 매우 다양합니다: (1) strip()으로 공백 제거, (2) lower()/upper()로 대소문자 변환, (3) replace()로 문자 치환, (4) split()으로 문자열 분리, (5) contains()로 특정 패턴 검색. 이런 메서드들을 조합하면 복잡한 텍스트 정제도 쉽게 할 수 있습니다.
코드 예제
import pandas as pd
# 정제가 필요한 문자열 데이터
df = pd.DataFrame({
'name': [' 김철수 ', 'LEE YOUNGHEE', 'park minsu '],
'email': ['kim@GMAIL.com', 'lee@naver.com', 'park@DAUM.net'],
'phone': ['010-1234-5678', '010.9876.5432', '01012345678'],
'company': ['Apple Inc.', 'Google LLC', 'Microsoft Corp']
})
# 공백 제거 및 소문자로 통일
df['name'] = df['name'].str.strip().str.title()
# 이메일 소문자로 통일 및 도메인 추출
df['email'] = df['email'].str.lower()
df['domain'] = df['email'].str.split('@').str[1]
# 전화번호 형식 통일 (숫자만 남기기)
df['phone'] = df['phone'].str.replace(r'[^0-9]', '', regex=True)
# 회사명에서 Inc., LLC 등 제거
df['company'] = df['company'].str.replace(r'\s+(Inc\.|LLC|Corp).*', '', regex=True)
설명
이것이 하는 일: 문자열 데이터의 불필요한 요소를 제거하고, 형식을 통일하고, 필요한 부분만 추출하여 일관되고 깨끗한 데이터로 만듭니다. 첫 번째로, str.strip()은 문자열 앞뒤의 공백을 제거합니다.
" 김철수 "가 "김철수"가 되는 거죠. 그 다음 str.title()은 각 단어의 첫 글자를 대문자로 만들어 이름을 보기 좋게 만듭니다.
이렇게 연속으로 메서드를 연결하는 걸 메서드 체이닝이라고 합니다. 두 번째로, 이메일 주소를 str.lower()로 모두 소문자로 통일합니다.
이메일은 대소문자를 구분하지 않으므로 통일하는 게 좋습니다. 그 다음 str.split('@')로 @를 기준으로 나누면 ['kim', 'gmail.com']처럼 리스트가 되고, .str[1]로 두 번째 요소인 도메인만 추출합니다.
세 번째로, 전화번호는 하이픈, 점 등 형식이 제각각입니다. str.replace()에 정규표현식 r'[^0-9]'를 사용하면 "숫자가 아닌 모든 문자"를 공백으로 바꿔서 제거합니다.
결과적으로 숫자만 남아서 "01012345678" 형식으로 통일됩니다. 네 번째로, 회사명에서 Inc., LLC 같은 법인 형태 표시를 제거합니다.
정규표현식 r'\s+(Inc.|LLC|Corp).*'은 "공백 다음에 Inc., LLC, 또는 Corp가 오고 그 뒤에 뭐가 오든"을 의미합니다. 이걸 공백으로 바꾸면 "Apple"만 남습니다.
여러분이 이런 정제 기법을 사용하면 데이터 품질이 크게 향상됩니다. 중복 제거와 그룹화가 정확해지고, 검색과 필터링이 쉬워지며, 사용자 입력 오류를 자동으로 수정할 수 있고, 일관된 형식으로 보고서를 만들 수 있습니다.
실전 팁
💡 정규표현식을 사용할 때는 반드시 regex=True 파라미터를 추가하세요. 안 그러면 일반 문자열로 인식됩니다
💡 str 메서드는 NaN 값이 있어도 에러를 내지 않습니다. 하지만 결과도 NaN이 되므로, 필요하면 먼저 fillna('')로 빈 문자열로 채우세요
💡 대량의 문자열 처리는 느릴 수 있습니다. apply() 대신 벡터화된 str 메서드를 사용하면 훨씬 빠릅니다
💡 한국어 이름에서 성과 이름을 분리하려면 str[0]으로 첫 글자(성), str[1:]로 나머지(이름)를 추출할 수 있습니다
💡 특정 패턴을 찾으려면 str.contains('패턴')을 사용하세요. 예를 들어 df[df['email'].str.contains('gmail')]로 Gmail 사용자만 필터링할 수 있습니다
5. 이상치_탐지_및_처리
시작하며
여러분이 학생들의 시험 점수를 분석하는데, 대부분 50~100점 사이인데 갑자기 -10점이나 500점이 튀어나온 걸 발견한 적 있나요? 또는 직원 나이 데이터에 200살이 있거나, 월급이 음수인 경우처럼요.
이런 이상치(outlier)는 입력 오류, 측정 오류, 시스템 버그 등으로 발생합니다. 이상치를 그대로 두면 평균, 표준편차 같은 통계값이 크게 왜곡되고, 머신러닝 모델의 성능도 떨어집니다.
예를 들어, 1억 원짜리 집이 대부분인 동네에 1000억 원 데이터가 하나 있으면, 평균 집값이 실제보다 훨씬 높게 계산됩니다. 바로 이럴 때 필요한 것이 이상치 탐지 및 처리입니다.
Pandas와 통계 기법을 활용하면 비정상적인 값들을 자동으로 찾아내고 적절히 처리할 수 있습니다.
개요
간단히 말해서, 이상치 탐지는 정상 범위를 벗어난 극단적인 값들을 찾아내고, 제거하거나 적절한 값으로 대체하는 과정입니다. 왜 이 작업이 중요할까요?
이상치는 소수지만 영향력은 큽니다. 99명의 정상 데이터와 1명의 이상치가 있을 때, 그 1명이 전체 평균을 완전히 바꿀 수 있습니다.
예를 들어, 고객 구매 금액을 분석할 때 실수로 입력된 1억 원 데이터가 있으면, 평균 구매액이 터무니없이 높아져서 마케팅 전략을 잘못 수립하게 됩니다. 기존에는 데이터를 정렬해서 눈으로 이상한 값을 찾았다면, 이제는 IQR(사분위수 범위)이나 Z-score 같은 통계 기법으로 자동 탐지할 수 있습니다.
이상치 탐지에는 주로 두 가지 방법을 사용합니다: (1) IQR 방법 - 데이터의 25%와 75% 지점을 기준으로 정상 범위를 정하는 방법, (2) Z-score 방법 - 평균에서 얼마나 떨어져 있는지로 판단하는 방법. 탐지 후에는 제거하거나, 최댓값/최솟값으로 대체하거나, 평균값으로 채우는 등 다양하게 처리할 수 있습니다.
코드 예제
import pandas as pd
import numpy as np
# 이상치가 포함된 데이터
df = pd.DataFrame({
'age': [25, 30, 28, 200, 32, 27, -5, 29],
'salary': [3000, 3500, 3200, 100000, 3100, 3300, 3400, 3250]
})
# 방법 1: IQR을 이용한 이상치 탐지
Q1 = df['salary'].quantile(0.25) # 1사분위수
Q3 = df['salary'].quantile(0.75) # 3사분위수
IQR = Q3 - Q1 # 사분위수 범위
# 정상 범위 계산
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 이상치 필터링
df_filtered = df[(df['salary'] >= lower_bound) & (df['salary'] <= upper_bound)]
# 방법 2: Z-score를 이용한 이상치 탐지
z_scores = np.abs((df['age'] - df['age'].mean()) / df['age'].std())
df_no_outliers = df[z_scores < 3] # Z-score가 3 미만인 데이터만
설명
이것이 하는 일: 통계적 기법을 사용하여 데이터에서 비정상적으로 크거나 작은 값들을 자동으로 찾아내고, 분석에 방해가 되지 않도록 제거하거나 처리합니다. 첫 번째로, IQR 방법은 데이터를 크기순으로 정렬했을 때 25% 지점(Q1)과 75% 지점(Q3)을 찾습니다.
이 둘의 차이가 IQR인데, 이는 데이터의 중간 50%가 분포한 범위를 나타냅니다. 마치 반에서 성적 중위권 학생들의 점수 범위를 보는 것과 같습니다.
두 번째로, 정상 범위를 Q1 - 1.5IQR부터 Q3 + 1.5IQR까지로 정의합니다. 이 1.5라는 숫자는 통계학에서 널리 쓰이는 기준으로, 대부분의 정상 데이터를 포함하면서 극단적인 값은 걸러냅니다.
salary가 100,000인 경우 이 범위를 크게 벗어나므로 이상치로 판단됩니다. 세 번째로, Z-score 방법은 각 값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지 계산합니다.
Z-score가 3보다 크다는 건 평균에서 표준편차의 3배 이상 떨어져 있다는 뜻으로, 정규분포에서는 전체의 0.3%밖에 안 되는 극단값입니다. age가 200이나 -5인 경우 Z-score가 매우 크므로 이상치로 검출됩니다.
네 번째로, 이상치를 찾은 후에는 해당 행을 제거합니다. 조건식 (df['salary'] >= lower_bound) & (df['salary'] <= upper_bound)를 사용하여 정상 범위 안에 있는 데이터만 선택하면, 이상치가 제거된 깨끗한 데이터프레임을 얻을 수 있습니다.
여러분이 이 기법들을 활용하면 데이터 분석의 정확도가 크게 향상됩니다. 왜곡된 통계값을 방지하고, 머신러닝 모델의 성능을 개선하며, 데이터 품질 문제를 조기에 발견하고, 신뢰할 수 있는 인사이트를 도출할 수 있습니다.
실전 팁
💡 IQR의 1.5 배수는 기본값이지만, 더 엄격하게 하려면 1.0, 더 관대하게 하려면 2.0이나 3.0을 사용할 수 있습니다
💡 이상치를 무조건 제거하지 마세요. 때로는 의미 있는 정보일 수 있습니다. 예를 들어 VIP 고객의 고액 구매는 이상치지만 중요한 데이터입니다
💡 여러 열에 동시에 이상치 탐지를 적용하려면 for 루프나 apply()를 활용하세요
💡 이상치를 제거 대신 윈저화(winsorization)로 처리할 수도 있습니다. 극단값을 특정 백분위수 값으로 대체하는 방법입니다
💡 시각화를 활용하세요. boxplot이나 scatter plot으로 이상치를 눈으로 확인하면 통계값만으로 놓칠 수 있는 패턴을 발견할 수 있습니다
6. 데이터_정규화
시작하며
여러분이 키(cm)와 몸무게(kg)로 건강 점수를 계산하려고 하는데, 키는 150190 범위고 몸무게는 40100 범위라서 둘을 공정하게 비교할 수 없는 상황을 겪어본 적 있나요? 이런 스케일 차이 문제는 머신러닝에서 매우 중요합니다.
연봉(수천만 원)과 나이(수십 년)를 함께 사용하면, 연봉의 숫자가 훨씬 크기 때문에 나이의 영향력이 묻혀버립니다. 마치 1원짜리 동전과 1만원권 지폐를 같은 개수로 비교하는 것처럼, 공정한 비교가 되지 않습니다.
바로 이럴 때 필요한 것이 데이터 정규화입니다. 서로 다른 범위의 데이터들을 같은 스케일로 맞춰주면, 공정한 비교와 정확한 분석이 가능해집니다.
개요
간단히 말해서, 데이터 정규화는 서로 다른 범위를 가진 여러 변수들을 동일한 스케일(보통 0~1 또는 평균 0, 표준편차 1)로 변환하는 과정입니다. 왜 이 작업이 필수일까요?
많은 머신러닝 알고리즘은 거리를 계산하는데, 스케일이 다르면 큰 값이 결과를 지배합니다. 예를 들어, 집값 예측 모델에서 면적(30100㎡)과 가격(110억 원)을 함께 사용하면, 가격의 숫자가 훨씬 크므로 면적의 영향이 거의 무시됩니다.
또한 경사하강법 같은 최적화 알고리즘도 정규화된 데이터에서 훨씬 빠르게 수렴합니다. 기존에는 손으로 최솟값과 최댓값을 계산해서 변환했다면, 이제는 Pandas와 scikit-learn으로 자동화할 수 있습니다.
주로 두 가지 정규화 방법을 사용합니다: (1) Min-Max 정규화 - 데이터를 0과 1 사이로 변환, (2) 표준화(Z-score) - 평균을 0으로, 표준편차를 1로 변환. Min-Max는 범위가 명확할 때 좋고, 표준화는 이상치에 덜 민감하므로 더 자주 사용됩니다.
코드 예제
import pandas as pd
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# 스케일이 다른 데이터
df = pd.DataFrame({
'age': [25, 30, 35, 40, 45],
'salary': [3000000, 3500000, 4000000, 4500000, 5000000],
'experience': [2, 5, 8, 12, 15]
})
# 방법 1: Min-Max 정규화 (0~1 사이로)
scaler_minmax = MinMaxScaler()
df_minmax = pd.DataFrame(
scaler_minmax.fit_transform(df),
columns=df.columns
)
# 방법 2: 표준화 (평균=0, 표준편차=1)
scaler_standard = StandardScaler()
df_standard = pd.DataFrame(
scaler_standard.fit_transform(df),
columns=df.columns
)
# 방법 3: 수동 Min-Max 정규화
df_manual = (df - df.min()) / (df.max() - df.min())
설명
이것이 하는 일: 서로 다른 단위와 범위를 가진 여러 변수들을 동일한 기준으로 변환하여, 어느 한 변수가 다른 변수를 압도하지 않도록 공정한 비교가 가능하게 만듭니다. 첫 번째로, MinMaxScaler는 각 열의 최솟값을 0, 최댓값을 1로 만들고 나머지 값들을 그 사이에 비례적으로 배치합니다.
예를 들어, 나이가 2545 범위라면, 25는 0, 45는 1, 35는 0.5가 됩니다. 연봉도 마찬가지로 01 범위로 바뀌므로, 이제 나이와 연봉을 공정하게 비교할 수 있습니다.
두 번째로, StandardScaler는 각 열의 평균을 0으로, 표준편차를 1로 만듭니다. 이는 각 값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지로 표현하는 것입니다.
평균보다 큰 값은 양수, 작은 값은 음수가 되며, 대부분의 값이 -3에서 +3 사이에 분포합니다. 이 방법은 이상치의 영향을 덜 받아서 더 안정적입니다.
세 번째로, fit_transform()은 두 단계를 한 번에 수행합니다. fit()은 최솟값, 최댓값, 평균, 표준편차 같은 통계값을 계산하여 저장하고, transform()은 그 통계값을 사용하여 실제 변환을 수행합니다.
나중에 새로운 데이터가 들어오면 저장된 통계값으로 똑같이 변환할 수 있어 일관성을 유지할 수 있습니다. 네 번째로, 수동 방식은 (값 - 최솟값) / (최댓값 - 최솟값) 공식을 사용합니다.
이는 Min-Max 정규화의 수학적 원리를 직접 구현한 것으로, 각 값이 전체 범위에서 어디에 위치하는지 비율로 나타냅니다. 여러분이 정규화를 적용하면 머신러닝 모델의 성능이 크게 향상됩니다.
모든 변수가 공정하게 기여하고, 학습 속도가 빨라지며, 수치 안정성이 개선되고, 변수 간 비교가 의미 있어집니다.
실전 팁
💡 Min-Max는 이상치에 민감합니다. 극단값 하나가 전체 범위를 넓혀서 대부분의 값이 좁은 범위에 몰릴 수 있으므로, 이상치가 있다면 표준화를 사용하세요
💡 학습 데이터로 fit()한 scaler를 저장해두고, 테스트 데이터에는 transform()만 적용하세요. 데이터 누수(data leakage)를 방지할 수 있습니다
💡 정규화 전에 원본 데이터를 복사해두세요. 나중에 역변환(inverse_transform)이 필요할 수 있습니다
💡 트리 기반 모델(Random Forest, XGBoost)은 정규화가 필요 없지만, 신경망이나 SVM, k-NN은 반드시 필요합니다
💡 시계열 데이터는 전체가 아닌 이동 윈도우 단위로 정규화하는 게 좋습니다. 미래 데이터 정보가 과거에 누수되는 걸 방지할 수 있습니다
7. 카테고리_인코딩
시작하며
여러분이 '남성', '여성' 같은 성별 데이터나 '서울', '부산', '대구' 같은 지역 데이터를 머신러닝 모델에 넣으려고 했는데, "문자열은 계산할 수 없습니다"라는 에러를 본 적 있나요? 이런 문제는 카테고리형 데이터를 다룰 때 항상 발생합니다.
컴퓨터는 숫자로만 계산할 수 있기 때문에, '빨강', '파랑', '초록' 같은 색상 정보를 숫자로 바꿔줘야 합니다. 하지만 단순히 '빨강'=1, '파랑'=2, '초록'=3으로 하면 문제가 생깁니다.
컴퓨터가 '초록'이 '빨강'보다 3배 크다고 잘못 인식할 수 있거든요. 바로 이럴 때 필요한 것이 카테고리 인코딩입니다.
카테고리 데이터를 올바른 방식으로 숫자로 변환하여, 의미를 왜곡하지 않으면서도 머신러닝 모델이 이해할 수 있게 만듭니다.
개요
간단히 말해서, 카테고리 인코딩은 '남성', '여성' 같은 범주형 데이터를 머신러닝 모델이 처리할 수 있는 숫자 형태로 변환하는 과정입니다. 왜 이 작업이 필요할까요?
대부분의 머신러닝 알고리즘은 숫자 입력만 받습니다. 예를 들어, 고객의 구매 예측 모델을 만들 때 성별, 지역, 선호 카테고리 같은 정보를 활용하려면 반드시 숫자로 변환해야 합니다.
하지만 단순 숫자 변환은 순서가 없는 카테고리에 잘못된 크기 관계를 부여할 수 있습니다. 기존에는 if-else 문으로 하나하나 코딩했다면, 이제는 Pandas와 scikit-learn의 인코더로 자동화할 수 있습니다.
주로 세 가지 인코딩 방법을 사용합니다: (1) 레이블 인코딩 - 각 카테고리를 0, 1, 2... 같은 정수로 변환 (순서가 있을 때), (2) 원-핫 인코딩 - 각 카테고리를 별도의 이진 열로 변환 (순서가 없을 때), (3) 빈도 인코딩 - 각 카테고리의 출현 빈도로 변환.
데이터의 특성에 맞게 선택해야 합니다.
코드 예제
import pandas as pd
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
# 카테고리형 데이터
df = pd.DataFrame({
'color': ['빨강', '파랑', '초록', '빨강', '파랑'],
'size': ['S', 'M', 'L', 'M', 'S'],
'city': ['서울', '부산', '대구', '서울', '부산']
})
# 방법 1: 레이블 인코딩 (순서가 있는 경우)
size_encoder = LabelEncoder()
df['size_encoded'] = size_encoder.fit_transform(df['size'])
# 방법 2: 원-핫 인코딩 (순서가 없는 경우)
df_onehot = pd.get_dummies(df, columns=['color', 'city'], prefix=['color', 'city'])
# 방법 3: 수동 매핑
size_map = {'S': 0, 'M': 1, 'L': 2}
df['size_manual'] = df['size'].map(size_map)
# 방법 4: 빈도 인코딩
df['city_freq'] = df['city'].map(df['city'].value_counts())
설명
이것이 하는 일: 문자열 형태의 범주형 데이터를 숫자로 변환하되, 카테고리 간의 관계(순서가 있는지 없는지)를 올바르게 반영하여 머신러닝 모델이 의미를 정확히 학습할 수 있게 합니다. 첫 번째로, 레이블 인코딩은 각 고유한 카테고리에 0부터 시작하는 정수를 할당합니다.
'S'=0, 'M'=1, 'L'=2처럼요. 이 방법은 Small < Medium < Large처럼 순서가 명확한 경우에 적합합니다.
LabelEncoder는 자동으로 알파벳 순서대로 번호를 매기므로, 순서가 중요하다면 수동 매핑을 사용하는 게 더 안전합니다. 두 번째로, 원-핫 인코딩은 각 카테고리를 별도의 열로 만들고 해당하면 1, 아니면 0을 넣습니다.
'빨강'이면 [1, 0, 0], '파랑'이면 [0, 1, 0], '초록'이면 [0, 0, 1]처럼요. 이렇게 하면 어떤 색이 다른 색보다 크다는 잘못된 관계가 생기지 않습니다.
get_dummies()는 이 과정을 자동으로 수행하며, prefix로 열 이름 앞에 접두어를 붙여 구분하기 쉽게 만듭니다. 세 번째로, 수동 매핑은 map() 함수로 직접 정의한 딕셔너리를 사용합니다.
size_map = {'S': 0, 'M': 1, 'L': 2}처럼 원하는 순서를 명확히 지정할 수 있어, 알파벳 순서가 아닌 의미 있는 순서를 반영할 수 있습니다. 네 번째로, 빈도 인코딩은 각 카테고리가 데이터에서 얼마나 자주 나타나는지를 값으로 사용합니다.
'서울'이 2번, '부산'이 2번, '대구'가 1번 나타나면, 서울과 부산은 2, 대구는 1로 인코딩됩니다. 이는 빈도 자체가 유용한 정보일 때 효과적입니다.
여러분이 올바른 인코딩을 사용하면 모델 성능이 크게 향상됩니다. 카테고리 정보를 효과적으로 활용할 수 있고, 잘못된 크기 관계를 방지하며, 다양한 카테고리 데이터를 처리할 수 있고, 데이터의 의미를 보존할 수 있습니다.
실전 팁
💡 원-핫 인코딩은 카테고리가 많으면 열이 너무 많아집니다. 100개 도시면 100개 열이 생기므로, 이럴 땐 타겟 인코딩이나 임베딩을 고려하세요
💡 원-핫 인코딩 후 drop_first=True 옵션을 사용하면 다중공선성을 방지할 수 있습니다. n개 카테고리를 n-1개 열로 표현합니다
💡 테스트 데이터에 학습 데이터에 없던 새 카테고리가 나타날 수 있습니다. handle_unknown='ignore' 옵션으로 대비하세요
💡 순서형 카테고리(Low, Medium, High)는 반드시 올바른 순서로 인코딩하세요. 잘못된 순서는 모델을 혼란스럽게 만듭니다
💡 카테고리가 50개 이상이면 원-핫 대신 임베딩(신경망) 또는 타겟 인코딩(부스팅 모델)을 사용하는 게 더 효율적입니다
8. 날짜_데이터_처리
시작하며
여러분이 판매 데이터를 분석하려고 하는데, 날짜가 '2024-01-15', '01/15/2024', '2024년 1월 15일'처럼 형식이 제각각이거나, 요일별 매출을 보고 싶은데 날짜에서 요일을 어떻게 추출해야 할지 막막한 적 있나요? 이런 날짜 데이터 처리는 시계열 분석에서 가장 기본이 되는 작업입니다.
로그 데이터, 거래 기록, 센서 데이터 등 대부분의 실무 데이터에는 시간 정보가 포함되어 있습니다. 하지만 날짜가 문자열로 저장되어 있으면 "3개월 후", "작년 같은 날"같은 계산을 할 수 없고, 월별 집계나 추세 분석도 불가능합니다.
바로 이럴 때 필요한 것이 날짜 데이터 처리입니다. Pandas의 강력한 날짜 기능을 사용하면 다양한 형식의 날짜를 파싱하고, 년/월/일/요일을 추출하고, 날짜 계산을 쉽게 할 수 있습니다.
개요
간단히 말해서, 날짜 데이터 처리는 문자열 형태의 날짜를 datetime 타입으로 변환하고, 거기서 필요한 정보(년, 월, 요일 등)를 추출하며, 날짜 간 계산을 수행하는 과정입니다. 왜 이 작업이 중요할까요?
날짜는 비즈니스 분석의 핵심 축입니다. 예를 들어, 월별 매출 추이를 보려면 날짜에서 월을 추출해야 하고, 요일별 방문자 패턴을 분석하려면 요일을 알아야 합니다.
또한 "가입 후 30일 내 구매율"처럼 기간 계산이 필요한 경우도 많습니다. 문자열 상태로는 이 어떤 것도 할 수 없습니다.
기존에는 문자열 파싱과 복잡한 계산을 직접 코딩했다면, 이제는 Pandas의 datetime 기능으로 간단히 처리할 수 있습니다. Pandas의 날짜 기능은 매우 다양합니다: (1) to_datetime()으로 다양한 형식 자동 파싱, (2) dt 접근자로 년/월/일/요일 추출, (3) 날짜 간 연산으로 기간 계산, (4) resample()로 시계열 집계.
이런 기능들로 복잡한 시계열 분석도 쉽게 할 수 있습니다.
코드 예제
import pandas as pd
# 다양한 형식의 날짜 데이터
df = pd.DataFrame({
'date_str': ['2024-01-15', '2024-02-20', '2024-03-25', '2024-04-30'],
'sales': [1000, 1500, 1200, 1800]
})
# 문자열을 datetime으로 변환
df['date'] = pd.to_datetime(df['date_str'])
# 년, 월, 일, 요일 추출
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['weekday'] = df['date'].dt.day_name() # 요일 이름
# 날짜 계산 (30일 후)
df['after_30days'] = df['date'] + pd.Timedelta(days=30)
# 날짜 차이 계산
df['days_from_first'] = (df['date'] - df['date'].min()).dt.days
설명
이것이 하는 일: 문자열 형태의 날짜를 Pandas가 이해하는 datetime 형식으로 변환하고, 거기서 다양한 시간 정보를 추출하며, 날짜 간 연산을 통해 의미 있는 시계열 분석을 가능하게 합니다. 첫 번째로, to_datetime()은 다양한 형식의 날짜 문자열을 자동으로 인식하여 datetime 타입으로 변환합니다.
'2024-01-15', '01/15/2024', '2024년 1월 15일' 모두 처리할 수 있습니다. 변환 후에는 날짜로서의 모든 기능을 사용할 수 있게 됩니다.
두 번째로, dt 접근자는 datetime 열에서 다양한 정보를 추출하는 강력한 도구입니다. dt.year는 년도를, dt.month는 월을, dt.day는 일을 가져옵니다.
dt.day_name()은 'Monday', 'Tuesday' 같은 요일 이름을 반환하는데, 이를 활용하면 "월요일에 매출이 높다"같은 인사이트를 찾을 수 있습니다. 세 번째로, Timedelta를 사용하면 날짜에 일정 기간을 더하거나 뺄 수 있습니다.
pd.Timedelta(days=30)은 30일을 의미하므로, 날짜에 더하면 정확히 30일 후의 날짜를 계산합니다. 이는 만료일 계산, 예정일 설정 등에 유용합니다.
네 번째로, 날짜 간 빼기 연산은 Timedelta를 반환합니다. 각 날짜에서 첫 번째 날짜를 빼면 "첫날로부터 며칠 지났는지"를 계산할 수 있고, .dt.days로 일수만 추출할 수 있습니다.
이는 가입일로부터 경과일, 이벤트까지 남은 기간 등을 계산할 때 필수적입니다. 여러분이 이런 날짜 처리 기능을 활용하면 강력한 시계열 분석이 가능해집니다.
월별/요일별 트렌드를 파악하고, 기간별 통계를 쉽게 계산하며, 날짜 기반 필터링과 정렬을 자유롭게 하고, 시간 패턴을 발견하여 예측 모델을 개선할 수 있습니다.
실전 팁
💡 날짜 형식이 일정하다면 format='%Y-%m-%d'로 명시하면 파싱 속도가 10배 이상 빨라집니다
💡 잘못된 날짜 형식이 섞여 있다면 errors='coerce'를 사용하여 NaN으로 변환하세요. 에러로 전체 변환이 실패하는 걸 방지합니다
💡 시간대(timezone)가 중요한 경우 tz_localize()로 시간대를 지정하고 tz_convert()로 변환하세요
💡 월말, 분기말 같은 특정 날짜를 다루려면 pd.offsets.MonthEnd()같은 오프셋 객체를 활용하세요
💡 시계열 데이터는 날짜를 인덱스로 설정하면(set_index) resample(), rolling() 같은 강력한 시계열 함수를 사용할 수 있습니다
댓글 (0)
함께 보면 좋은 카드 뉴스
데이터 증강과 정규화 완벽 가이드
머신러닝 모델의 성능을 극대화하는 핵심 기법인 데이터 증강과 정규화에 대해 알아봅니다. 실무에서 바로 활용할 수 있는 다양한 기법과 실전 예제를 통해 과적합을 방지하고 모델 성능을 향상시키는 방법을 배웁니다.
ResNet과 Skip Connection 완벽 가이드
딥러닝 모델이 깊어질수록 성능이 떨어지는 문제를 해결한 혁신적인 기법, ResNet과 Skip Connection을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실제 구현 코드와 함께 배워보세요.
CNN 아키텍처 완벽 가이드 LeNet AlexNet VGGNet
컴퓨터 비전의 기초가 되는 세 가지 핵심 CNN 아키텍처를 배웁니다. 손글씨 인식부터 이미지 분류까지, 딥러닝의 발전 과정을 따라가며 각 모델의 구조와 특징을 실습 코드와 함께 이해합니다.
CNN 기초 Convolution과 Pooling 완벽 가이드
CNN의 핵심인 Convolution과 Pooling을 초급자도 쉽게 이해할 수 있도록 설명합니다. 이미지 인식의 원리부터 실제 코드 구현까지, 실무에서 바로 활용 가능한 내용을 담았습니다.
TensorFlow와 Keras 완벽 입문 가이드
머신러닝과 딥러닝의 세계로 들어가는 첫걸음! TensorFlow와 Keras 프레임워크를 처음 접하는 분들을 위한 친절한 가이드입니다. 실무에서 바로 활용할 수 있는 핵심 개념과 예제를 통해 AI 모델 개발의 기초를 탄탄히 다져보세요.