251118_BootCamp Day 22_이상치, 결측치 처리

2025. 11. 18. 21:07TIL

1. 이상치(outliers), 결측치(NaN)
이상치 : 전체 데이터 범위에서 벗어난 아주 작은 값이나 아주 큰 값
결측치 : 데이터 수집 과정에서 측정되지 않거나 누락된 데이터
이상치와 결측치는 데이터 분석 전, EDA 과정에서 반드시 처리되어야 한다.
이러한 과정을 EDA (explorative data analysis, 탐색적 데이터 분석) 과정 중 하나인 data cleaning(데이터 정제)라고 한다.

결측치는 데이터가 수집/저장되는 과정에서 HW/SW적 문제로 인해 발생하게 된다.
이러한 결측치를 처리하지 않는 경우, 데이터 분석 과정에서 잘못된 결과를 도출할 수 있기 때문에 반드시 처리해줘야 한다.
결측치는 제거하거나 대체할 수 있다.
결측치를 대체하는 것은 위험하며, 대부분의 경우 수행하지 않는다. (어떤 값인지 알 수 없기 때문에 어떤 값을 넣어야 할지도 정확히 모르기 때문)

2. 결측치 제거

# 결측치 제거 방법 1 : 열 제거하기
# Unnamed: 4 컬럼과 같이 전체 열이 결측치인 경우, 해당 열을 제거할 수 있다.
df3 = df3.drop('Unnamed: 4', axis=1)
# 결측치를 제거한 후에는 반드시 제대로 수행되었는지 확인해야 한다.
df3.isnull().sum()
# Unnamed: 4 열이 정상적으로 제거된 것을 확인할 수 있다.
# 결측치 제거 방법 2 : 결측치가 있는 행들을 모두 제거
df3.dropna(inplace=True)
# inplace=True 커맨드를 사용하면 데이터 대입 없이도 메서드 결과가 자동으로 저장된다.
# 같은 식 : df3 = df3.dropna()

 

3. 결측치 대체 1 : 최빈값

# 최빈값 대체 : 해당 컬럼의 최빈값을 구함.
df3['Interaction type'].mode()

mode : 대상 행/열의 최빈값을 구하는 메서드.
axis : 최빈값을 구할 축. = 0 : index / = 1 : columns
numeric_only : = True일 경우 숫자, 소수, Bool 값이 있는 열에 대해서만 연산 수행.
dropna : 결측치를 계산에서 제외할 지 여부 결정.

# 최빈값 검증
df3.groupby('Interaction type').count()
# Interaction type 컬럼에서 like가 가장 많은 것을 확인.
# 최빈값으로 결측치 채우기
df3 = df3['Interaction type'].fillna(df3['Interaction type'].mode().iloc[0])

fillna 함수를 이용해 채워줄 값을 결정.
mode 함수는 한 컬럼에 대해 수행한 경우, Series(데이터프레임에 대해 수행한 경우 DataFrame) 형태로 나오기 때문에, 인덱싱을 통해 값을 지정해주어야 한다.

 

4. 결측치 대체 2 : 평균

현재 Shipping Weight는 '숫자 + 단위'가 문자열 형태로 저장되어 있다.
str.split을 통해 우선 문자열을 분리하고, 리스트로 반환된 값 중 첫 번째 인덱스(숫자)만 가져온다.
df['Shipping Weight'].str.split()[0]이라고 작성하면? => 리스트로 반환된 컬럼값들 중 첫 번째 컬럼의 값만 가져온다.
df['Shipping Weight'].str.split().str[0]이라고 작성하면? => split을 통해 분리된 컬럼 전체의 값을 가져온다.
df['Shipping Weight'].str.split().str[1]이라고 작성하면? => 뒤의 단위만 가져온다.

# Shipping Weight의 컬럼값을 분리하고, 숫자(아직 문자형) 부분만 반환
df['sw'] = df['Shipping Weight'].str.split().str[0]
# 문자형(string)을 실수(float) 형태로 변환
# 그냥 변환하면 에러가 발생하기 때문에, 에러를 무시하는 errors='coerce' 구문을 넣어준다.
df['sw'] = pd.to_numeric(df['sw'], errors='coerce')
# 평균값 대체
# fillna 함수에 sw 컬럼의 결측치를 제외하고 계산한 평균값을 넣어, 결측치를 모두 평균값으로 대체한다.
df['sw'] = df['sw'].fillna(df['sw'].mean())
# 결측치 대체 확인
df.isnull().sum()
# sw 컬럼의 결측치 개수가 0인 것을 확인할 수 있다.

 

5. 이상치 확인 1 : z-score

z-score (표준 점수) : 통계학에서 데이터 값이 평균으로부터 표준편차의 몇 배만큼 떨어져 있는지를 나타내는 표준화된 값.
z-score = (값 - 평균) / 표준편차
z-score = 1 => 해당 값은 평균으로부터 1 표준편차만큼 떨어져 있다.
표준화(standardization) : 표준정규분포(평균 = 0, 표준편차 = 1)의 속성을 갖도록 값들을 재조정하는 것.
표준화를 수행하면 각 데이터들은 평균을 기준으로 얼마나 떨어져 있는지를 나타내는 값으로 변환.
파이썬에서 표준화는 scikit-learn 라이브러리의 StandardScaler 함수를 이용해 수행할 수 있다.

# 표준화 진행을 위한 라이브러리 선언
from sklearn.preprocessing import StandardScaler
df['sw'] = df['Shipping Weight'].str.split().str[0]
df['sw'] = pd.to_numeric(df['sw'] , errors='coerce').fillna(0.0).astype(int)
# z-score를 적용할 컬럼 선정
df1 = df[['sw']]
# 표준화 진행
scale_df = StandardScaler().fit_transform(df1)
# 표준화된 컬럼을 원래 결과와 결합
merge_df = pd.concat([df1, pd.DataFrame(scale_df)], axis=1)

 

이상치 판별하기
일반적으로 z-score가 3보다 크거나 -3보다 작은 경우 이상치로 판별함.

# 이상치 판별 조건 만들기
mask = ((merge_df['zscore']<-3) | (merge_df['zscore']>3))
# 이상치만 모아둔 컬럼 만들기
outlier_df = merge_df[mask]
# z-score로 판별한 이상치 개수 : 55개
outlier_df.count()

 

6. 이상치 확인 2 : IQR(interquartile range)

IQR은 데이터의 분포가 정규분포를 따르지 않을 때 사용할 수 있다.
데이터의 25% 지점(Q1), 75% 지점(Q3) 사이의 범위(IQR, interquartile rang)를 사용하며, Q1-1.5IQR과 Q3+1.5IQR을 벗어나는 값을 모두 이상치로 간주한다.
IQR에서 데이터의 25% 지점, 75% 지점은 처음부터 끝까지 데이터를 크기 순으로 줄세웠을 때, 25%에 있는 값과 75%에 있는 값이다. IQR은 데이터 시각화에서 배우는 box plot을 통해서도 확인할 수 있다.

# Q1, Q3, IQR값 구하기
# quantile 함수를 이용해 쉽게 백분위수를 구할 수 있다.
q3 = df1['sw'].quantile(0.75)
q1 = df1['sw'].quantile(0.25)
iqr = q3 - q1

q3, q1, iqr
# 이상치 판별 함수 정의
def is_outlier(df1):
    score = df1['sw']
    if score > q3 + 1.5 * iqr or score < q1 - 1.5 * iqr:
        return 'outlier'
    else:
        return 'normal'
# apply 함수를 이용해 sw 컬럼의 값이 이상치인지 확인하고, 새로운 열에 판별 여부를 저장
df1['outlier'] = df1.apply(is_outlier, axis = 1)
# 이상치 개수 확인
df1.groupby('outlier').count()
# IQR 방식으로 계산한 이상치는 총 349개이다.

 

7. 이상치 확인 시 주의 사항

이처럼, 이상치는 계산 방식에 따라 그 수가 다를 수 있고, 이상치에 포함되는 값이 달라질 수도 있다.
데이터 분석가는 이상치를 매우 보수적으로 다뤄야 한다. (True인 값을 False로 판단할 수 있기 때문)
ex) 정상적인 게임 로그를 가진 유저들을 비정상(이상치)으로 판단해 제재를 가한다면? => 회사에 대한 유저들의 신뢰가 깨질 수 있음.
따라서 이상치 판단은 파이썬의 계산만 믿지 말고, 주관적으로 생각해서 판단해야 하며, 다른 동료들과 의견을 충분히 의견을 나누고 결정해야 한다.
또한 이상치는 중요한 의미를 나타내는 데이터를 포함할 수 있으므로 (ex. 백화점 VIP 고객의 구매 금액) 이상치를 따로 모아놓고 계산하는 것도 고려해야 한다.