Data_10기

데이터 전처리 & 시각화 - 결측치, 이상치

senu 2025. 11. 18. 21:18

오늘은 데이터 분석 기초 단계인 EDA에서 빠질 수 없는 결측치와 이상치를 다루는 방법에 대해 배웠다. 현업에서 만나게 될 데이터는 깨끗하지 않을 수 있기 때문에 분석을 시작하기 전에 데이터 품질을 점검하고, 이상치를 다루는 과정이 필수적이다.


1️⃣ 결측치 다루기

오늘도 마찬가지로 먼저 분석에 필요한 라이브러리와 서로 다른 csv 파일 여러개를 불러온 뒤, 실습에 들어갔다.

import pandas as pd 
import numpy as np
import time
from PIL import Image

import warnings
warnings.filterwarnings(action='ignore')

df = pd.read_csv("product_details.csv")
df2 = pd.read_csv("customer_details.csv")
df3 = pd.read_csv("E-commerece sales data 2024.csv")

 

데이터를 불러오면 가장 먼저 해야할 일은 컬럼 구조 확인, 결측치 유무 파악, 데이터 타입 점검이다.

 

1. 결측치 제거 (Drop)

데이터를 확인해보니 df3에는 필요 없는 커럼('Unnamed: 4')이 있었고, 여러 곳에서 결측치도 존재했다.

 

1) 특정 컬럼 삭제

df3 = df3.drop('Unnamed: 4', axis=1)

 

axis = 1 은 열 기준 삭제를 의미한다.

 

2) 컬럼별 결측치 개수 확인

df3.isnull().sum()

 

3) 결측치가 있는 행 전체 삭제 (Drop NA)

df3.dropna(inplace=True)

 

inplace = True 를 쓰면 원본 df3 자체가 수정되므로 의도한 경우에만 사용하는 것이 좋다.

 

 

2. 결측치 대체 - 최빈값(mode)

df3를 다시 불러오면 'Interaction type' 컬럼에 423개의 결측치가 있었다. 문자형 변수의 결측치는 평균이나 중앙값보다 최빈값(mode)으로 채우는 경우가 많다.

 

1) 최빈값 계산

df3['Interaction type'].mode()

 

2) 최빈값으로 결측치 채우기

df3['Interaction type'] = 
    df3['Interaction type'].fillna(df3['Interaction type'].mode().iloc[0])

 

여기서 .mode( ).iloc[0] 은 최빈값(mode 결과가 여러 개일 수 있으므로) 그중 첫 번째 값을 가져오겠다는 의미이다.

 

3) 인덱스 재정렬

df3 = df3.reset_index()

 

결측치가 많았던 컬럼이 최빈값으로 채워지면서 전체 데이터 수가 정상적으로 유지된 것을 확인할 수 있었다.

 

 

3. 결측치 대체 - 평균(mean), 중앙값(median)

이번에는 product_details.csv에서 Shipping Weight 결측치를 처리했다. 이 컬럼은 "1.2 pound" 처럼 문자열 형태라 먼저 숫자만 분리해 float로 변환하는 과정이 필요했다.

 

1) 숫자만 추출

df['sw'] = df['Shipping Weight'].str.split().str[0]

 

2) 문자열 → 숫자로 변환

df['sw'] = pd.to_numeric(df['sw'], errors='coerce')

 

errors='coerce'는 숫자로 변환할 수 없는 값은 자동으로  NaN으로 처리한다.

 

3) 평균값으로 대체

df['sw'] = df['sw'].fillna(df['sw'].mean())

 

4) 중앙값으로 대체

df['sw'] = df['sw'].fillna(df['sw'].median())

 

5) 조건별 대체 (Group-by)

이전보다 한 단계 더 고급 방식으로, Amazon 판매자 여부(Is Amazon Seller)에 따라 서로 다른 중앙값으로 대체할 수도 있다.

df['sw'] = df['sw'].fillna(
    df.groupby('Is Amazon Seller')['sw'].transform('median')
)

 

groupby + transform은 데이터의 상황에 맞게 더 세밀한 대체가 가능하도록 도와준다.

 


2️⃣ 이상치 다루기

결측치 처리 다음으로 중요한 단계는 이상치 탐지(outlier detection) 다. 이상치는 단순 실수일 수도 있고, 실제로 특이한 데이터를 의미할 수도 있다. 중요한 것은 분석 목적에 맞게 의도적으로 처리하는 것이다. 보통은 여러 방법을 비교하고 이상치가 가장 적은 방법을 채택한다고 한다.

 

1. Z-Score를 이용한 이상치 탐지

Z-score는 통계학에서 가장 널리 사용되는 이상치 판별 방법 중 하나이다.

 

1) Z-score 의미

  • 평균을 0, 표준편차를 1로 변환한 값
  • 각 데이터가 평균에서 얼마나 떨어져 있는지를 기준으로 판단
  • |z| > 3 이면 이상치로 보는 것이 일반적

2) Shipping Weight 컬럼 정리

df['sw'] = df['Shipping Weight'].str.split().str[0]
df['sw'] = pd.to_numeric(df['sw'], errors='coerce').fillna(0).astype(int)

 

3) 표준화

scale_df = StandardScaler().fit_transform(df[['sw']])

 

4) zscore 컬럼 생성 후 merge

merge_df = pd.concat([df[['sw']], pd.DataFrame(scale_df)], axis=1)
merge_df.columns = ['Shipping Weight', 'zscore']

 

5) 이상치 조건

mask = ((merge_df['zscore'] < -3) | (merge_df['zscore'] > 3))
strange_df = merge_df[mask]

 

 

2. IQR(사분위 범위)을 이용한 이상치 탐지

IQR 방식은 Q3 + 1.5 * IQR 초과하거나 Q1 - 1.5 * IQR 미만인 값을 이상치로 본다.

 

1) 사분위수 계산

q3 = df1['sw'].quantile(0.75)
q1 = df1['sw'].quantile(0.25)
iqr = q3 - q1

 

2) 이상치 조건 정의

def is_outlier(row):
    score = row['sw']
    if score > q3 + 1.5*iqr or score < q1 - 1.5*iqr:
        return '이상치'
    else:
        return '이상치아님'

 

3) apply 적용

df1['이상치여부'] = df1.apply(is_outlier, axis=1)

 


마무리

연구할 때 주로 R로 다뤘던 부분을 python에서 다루니 감회가 새로웠다. 아직 모든 방법이 손에 익지 않아 여전히 불안감은 있지만 반복해서 연습하고 복습하다보면 괜찮아 질 날이 있을 거라 기대해본다.