데이터 분석 TIL - 이상치와 결측치 처리

테리·2025년 11월 18일
post-thumbnail

1. 학습 키워드

  • 이상치
    dropna(), z-score, IQR
  • 결측치
    isnull(), .str.split(), .to_numeric(), transform()

2. 학습 내용

이상치: 명확한 기준은 없지만 일반적인 데이터들과 달리 매우 크거나 작은 값
결측치: 데이터 수집 과정에서 측정되지 않거나 누락된 데이터

결측치

결측치 처리에는 두가지 방법이 있다.
제거 or 대체. 그렇다면 제거하는 것이 좋을까? 대체하는 것이 좋을까?
결측치를 갖고 있는 데이터는 일반적으로 제거하고 분석하는 것이 좋다. 다른 값으로 함부로 대체한다면 데이터 왜곡으로 다른 분석 결과가 나올 수 있기에 제거하는 것이 가장 바람직하다.

결측치 확인: isnull()

df3.isnull().sum()

결측치 제거

열 제거하기: drop()

df.drop('컬럼명', axis=1)

결측치가 있는 행, 열 제거: dropna()

# 결측치가 있는 행 모두 제거
# axis=0 이 기본값.
df.dropna(axis=0, how='any') # 행 전체가 결측인 경우로 하려면 how='all'

# 결측치가 있는 열 모두 제거
df.dropna(axis=1)

# 결측치 제거 후 바로 저장
df.dropna(inplace=True)

결측치 대체

  • 평균값: mean()
  • 중간값: median()
    mean()과 median()은 숫자 데이터에만 가능하다. 문자에도 혹시 가능하나 해봤는데 역시 되지 않는다.
  • 바로 위 값으로 대체: fillna(method='ffill')
  • 바로 아래 값으로 대체: fillna(method='bfill')
    ex) df['sw'].fillna(method='bfill')
  • 최빈값: mode()
    mode() 함수는 시리즈를 output으로 가지기에 슬라이싱으로 단일값을 가져와야함.

mode()를 사용하기 전에 1차적으로 최빈값을 검증해 볼 수 있다.
컬럼을 groupby해서 개수를 세는 것이다. 이때 결측치는 카운트되지 않는다.

df3.groupby('Interaction type').count()


fillna()를 통해 Interaction type 컬럼의 최반값으로 null 값을 채우면 결과는 아래와 같다.

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

그런데 못생기지 않았는가?
여기에서 조금 더 보기 편하게 보려면 reset_index()를 사용하면 된다. 나는 reset_index()를 보통 데이터프레임 수정 후 다시 제대로 정렬할 때만 사용했는데 시리즈 형태의 결과를 보기 편하게 보려고 활용 하기도 하는 것 같다.

마지막 데이터 프레임 확인

추가 메모

데이터를 다루다보면 Series 형태의 데이터를 만나게 된다. 이때 str을 통해서 각 행의 데이터를 조금 더 손쉽게 다룰 수 있는 방법이 있다.

결측치 평균 값으로 대체하기

1) 데이터 타입 변환: .str.split().to_numeric() 사용

pandas series는 split()을 바로 사용할 수가 없어 str을 통해 전체 series에 접근할 수 있게함.

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

str.split()를 통해 행별로 문자열을 공백 기준으로 나눈 리스트를 추출하고 .str[0]을 통해 각 리스트의 0번째 값을 가져옴.

# string to float, 에러무시  
df['sw'] = pd.to_numeric(df['sw'] , errors='coerce')

2) 평균값으로 대체

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

groupby()와 transform()을 활용한 대체
transform()은 각 그룹에 연산을 적용한 뒤에 원래 행의 길이와 동일한 개수로 결과를 돌려주는 기능을 한다.

아래 이미지를 보면 'Is Amazon Seller'라는 컬럼은 'Y'와 'N'이라는 두 종류의 데이터가 있다.

Is Amazon Seller로 그룹을 나누고 각 그룹의 sw 컬럼의 중앙값을 구한다. 그리고 그 값들을 각 그룹에 그대로 뿌려줘 그룹별로 중앙값을 한번에 구하고 적용할 수 있다.

# Is Amazon Seller 를 기준으로 중앙값 계산하여 대체 
df['sw'] = df['sw'].fillna(df.groupby('Is Amazon Seller')['sw'].transform('median'))

이상치

정답이 없는(비지도 학습) 경우에는 계산에 의한 이상치만 갖고 이상치로 판단하는 것이 아니라 분석가의 주관적인 조건과 함께 결합해서 이상치를 판단하는 것이 필수다.

이상치 탐지: z-score(StandardScaler)

  • 평균으로부터 떨어져있는 정도를 통해 이상치를 판별하는 방법
  • 데이터의 분포가 정규분포를 이룰 때 데이터의 표준편차를 이용해 이상치를 판단하는 방법. 그렇기에 데이터 정규화 이후에 진행함.
  • scikit-learn 라이브러리가 이를 지원한다.
  • 표준 점수 Z가 -3이하 +3 이상이면 이상치로 판단한다.
  • Z = (값 - 평균) / 표준편차
from sklearn.preprocessing import StandardScaler

# z-score 를 적용할 컬럼 선정
df1 = df[['sw']]

scale_df = StandardScaler().fit_transform(df1)

이상치 탐지: IQR

백분위 수를 구하는 quantile() 함수를 통해 쉽게 구할 수 있으며 데이터프레임 전체에 대해서 구하면 각 열에 대한 값이 나오며 특정 열만 슬라이싱해서 구할수도 있다.

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

iqr = q3 - q1
q3, q1, iqr

이러한 형태로 사용할 수 있음.

# IQR : Q3 - Q1의 차이를 의미
# 이상치 : Q3 + 1.5 * IQR보다 높거나 Q1 - 1.5 * IQR보다 낮은 값을 의미

def is_outlier(df1):
    score = df1['sw']
    if score > 7 + (1.5 * 6) or score < 1 - (1.5 * 6):
        return '이상치'
    else:
        return '이상치아님'

# apply 함수를 통하여 각 값의 이상치 여부를 찾고 새로운 열에 결과 저장
df1['이상치여부'] = df1.apply(is_outlier, axis = 1) # axis = 1 지정 필수

Isolation Forest

  • 머신러닝 기법중 하나로 컬럼 개수가 많을때 이상치 탐지에 용이.
  • 결정트리 형태로 다른 관측치에 비해 경로 길이가 짧은 경우 이상치로 판단함.

DBScan

  • 데이터의 밀도 기반으로 군집을 형성. 군집에 포함되지 않는 데이터를 이상치로 탐지함.

3. 배운점

  1. 이상치에 대한 의사결정 진행시 z-score나 IQR 방식에만 의존하는 것이 아니라 데이터를 보는 사람의 주관적인 조건도 함께 추가되야 함을 알 수 있었다.
  2. 결측치 제거시에 dropna()를 자주 사용하는데 how 조건으로 행 전체를 다 제거할 수 있다.
  3. groupby()와 함께 tranform()을 사용하면 그룹별로 원하는 값을 동일하게 적용할 수 있다는 것을 알게됨.

0개의 댓글