본문 바로가기
[업무 지식]/Python

[이상치 처리] Python

by 에디터 윤슬 2024. 11. 7.
 

 

이상치 확인 boxplot

 
# boxplot 시각화 대상 컬럼 리스트
col = ['ARCH_YR_mean', 'STDG_CD', 'CGG_CD', 'ARCH_AREA', 'CTRT_DAY']

# 각 컬럼에 대해 Boxplot 생성
for column in col:
    plt.figure(figsize=(8, 4))  # 개별 플롯 크기 설정
    sns.boxplot(data=df_anal, x=column)
    plt.title(f'Boxplot of {column}')  # 제목 추가
    plt.show()  # 그래프 출력

 

이상치 탐지: Z-score

 
  • 함수로 한번에 적용
# 이상치 처리 z-score

def get_outlier_zscore(df):
    col = ['ARCH_YR_mean']
    zscore_cnt = {}
    from scipy import stats
    
    for column in col:
        mean = df[column].mean()
        std = df[column].std()
        z_scores = (df[column] - mean) / std
        abs_z_scores = np.abs(z_scores)
        threshold = 3
        outlier = abs_z_scores > threshold
        inlier = abs_z_scores <= threshold
        
        outlier_cnt = df[outlier].shape[0]
        zscore_cnt[column] = outlier_cnt
        
        value_mean = df.loc[inlier, column].mean().round(0)
        new_column = f'{column}_zscore'
        df[new_column] = df[column].mask(outlier, value_mean)
        
        print('컬럼별 이상치 갯수:', zscore_cnt)
        
    return df
        
get_outlier_zscore(df_anal)

 

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

from sklearn.preprocessing import StandardScaler

# 표준화 진행
# 표준화: 평균을 0으로, 표준 편차를 1로
# 데이터를 0을 중심으로 양쪽으로 데이터를 분포시키는 방법
# 표준화를 하게 되면 각 데이터들은 평균을 기준으로 얼마나 떨어져 있는지를 나타내는 값으로 변환
scale_df = StandardScaler().fit_transform(df1)
scale_df
# array 형태로 반환된 것을 dataframe으로 변환
scale_df = pd.DataFrame(scale_df)

# 기존 raw 값과 표준화 이후 데이터를 비교하기 위해 merge 진행
merged_df = pd.concat([df1, scale_df], axis = 1)
merged_df

# 컬럼명 변환
merged_df.columns = ['Shipping Weight', 'zscore']
# 이상치 감지
# Z-Score 기반, -3보다 작거나 3보다 큰 경우를 이상치로 판별
mask = ((merged_df['zscore'] < -3) | (merged_df['zscore'] > 3))

# mask 메소드 사용
strange_df = merged_df[mask]
strange_df
# 이상치를 제외한 데이터프레임 생성
df_cleaned = df[~strange_df.any(axis = 1)]
df_cleaned.info()

 

 

모든 숫자형 컬럼 한번에 Z-Score 적용

 
  • 모든 컬럼의 정규화(정규분포를 따르지 않는다면 적용)
# 모든 컬럼을 정규화하는 함수 정의 (0-100 범위로)
def normalize_data(df):
    return (df - df.min()) / (df.max() - df.min()) * 100
    
# 정규화된 데이터 생성
normalized_data = normalize_data(data)

 

# 원하지 않는 컬럼 문자형으로 변환
base_df[['article_id', 'product_code', 'price']] = base_df[['article_id', 'product_code', 'price']].astype('object')
base_df.dtypes

# 숫자형 컬럼만 선택 (select_dtypes 사용)
numeric_df = base_df.select_dtypes(include = ['int64', 'float'])
numeric_df
# StandardScaler 객체 생성 및 데이터 표준화
# array로 변환된 데이터를 데이터프레임으로
scale_df = StandardScaler().fit_transform(numeric_df)
scale_df = pd.DataFrame(scale_df)
scale_df

# 컬럼명 적용
numeric_df_col = numeric_df.columns
numeric_df_col

scale_df.columns = numeric_df_col
scale_df
# Z-score 절댓값이 3보다 큰 값 찾기 (이상치)
outliers = np.abs(scale_df) > 3

# 이상치가 있는 행 출력
outlier_rows = numeric_df[(outliers).any(axis=1)]
print("이상치가 포함된 행:")
print(outlier_rows)

 

# Z-score 절댓값이 3 이하인 값 찾기 (이상치가 없는 데이터)
non_outliers = np.abs(scale_df) <= 3

# 모든 열에서 이상치가 없는 행만 선택
non_outlier_rows = numeric_df[(non_outliers).all(axis=1)]
print("이상치가 없는 행:")
print(non_outlier_rows)

 

df_cleaned = base_df[~outliers.any(axis = 1)]
df_cleaned.info()
# article_id, product_code 숫자형으로 변환
df_cleaned[['article_id', 'product_code', 'price']] = df_cleaned[['article_id', 'product_code', 'price']].astype('float')
df_cleaned.dtypes

 

이상치 탐지: IQR

 
  • 한번에 적용하는 함수
# 이상치 처리 IQR

def get_outlier_iqr(df):
    col = ['ARCH_YR_mean']
    # 수치형 데이터: 'ARCH_YR_mean'
    # 결과를 저장할 딕셔너리
    iqr_cnt = {}
    for column in col:
        # 1사분위(Q1)와 3사분위(Q3) 계산
        Q1 = df[column].quantile(0.25)
        Q3 = df[column].quantile(0.75)
        IQR = Q3 - Q1  # IQR 계산

        # 이상치 경계값 정의
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR

        # 이상치가 아닌 값의 평균 계산
        mean_value = df[(df[column] >= lower_bound) & (df[column] <= upper_bound)][column].mean().round(0)
    
        # 이상치 값 갯수 파악하기 위한 데이터프레임 생성
        outlier_count = df[(df[column] < lower_bound) | (df[column] > upper_bound)].shape[0]
        iqr_cnt[column] = outlier_count

        # 이상치를 평균값으로 대체
        new_column = f'{column}_iqr'
        df[new_column] = df[column].mask((df[column] < lower_bound) | (df[column] > upper_bound), mean_value)

        print('컬럼별 이상치 갯수', iqr_cnt)
        return df
        

get_outlier_iqr(df_anal)

 

 

# Q3, Q1, IQR 값 구하기
# 백분위수를 구해주는 quantile 함수를 적용하여 쉽게 구할 수 있음
# 데이터프레임 전체 혹은 특정 열에 대하여 모두 적용이 가능

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

iqr = q3 - q1
q3, q1, iqr

 

# 이상치 판별 및 dataframe 저장 
# Q3 : 100개의 데이터로 가정 시, 25번째로 높은 값에 해당합니다.
# Q1 : 100개의 데이터로 가정 시, 75번째로 높은 값에 해당합니다.
# IQR : Q3 - Q1의 차이를 의미합니다.
# 이상치 : Q3 + 1.5 * IQR보다 높거나 Q1 - 1.5 * IQR보다 낮은 값을 의미

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

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

df1
# IQR 방식으로 구한 이상치 수량 확인
df1.groupby('이상치여부').count()

 

모든 숫자형 컬럼의 이상치 탐지: IQR

 
  • 박스플롯으로 이상치 분포 확인
# 박스 플롯 그리기
plt.figure(figsize=(12, 6))
data.boxplot()
plt.title('Box Plot of Each Column with Outliers')
plt.xlabel('Columns')
plt.ylabel('Values')
plt.xticks(rotation=45)
plt.grid(axis='y')
plt.tight_layout()
plt.show()
  • 각 컬럼 이상치 개수 확인
# IQR 기반 이상치 탐지 함수 정의
def detect_outliers(df):
    Q1 = df.quantile(0.25)
    Q3 = df.quantile(0.75)
    IQR = Q3 - Q1
    # 이상치 조건: Q1 - 1.5 * IQR 보다 작거나 Q3 + 1.5 * IQR 보다 큰 값들
    is_outlier = (df < (Q1 - 1.5 * IQR)) | (df > (Q3 + 1.5 * IQR))
    return is_outlier

# 각 숫자형 컬럼에 대해 이상치 여부를 계산
outliers = detect_outliers(numeric_df)

# 각 컬럼에서 이상치 개수 확인
outlier_counts = outliers.sum()
print("컬럼별 이상치 개수:")
print(outlier_counts)

 

  • 원본 데이터에 적용
# IQR 기반 이상치 탐지 함수 정의
def detect_outliers(df):
    Q1 = df.quantile(0.25)
    Q3 = df.quantile(0.75)
    IQR = Q3 - Q1
    # 이상치 조건: Q1 - 1.5 * IQR 보다 작거나 Q3 + 1.5 * IQR 보다 큰 값들
    is_outlier = (df < (Q1 - 1.5 * IQR)) | (df > (Q3 + 1.5 * IQR))
    return is_outlier

# 각 숫자형 컬럼에 대해 이상치 여부를 계산
outliers = detect_outliers(numeric_df)

# 각 컬럼에서 이상치 개수 확인
outlier_counts = outliers.sum()
print("컬럼별 이상치 개수:")
print(outlier_counts)

# 이상치를 제외한 클린한 데이터프레임 생성
clean_numeric_df = numeric_df[~outliers.any(axis=1)]
print(f"이상치를 제거한 후의 데이터프레임 크기: {clean_numeric_df.shape}")

# 각 컬럼의 이상치 출력
for col, outlier_data in outliers.items():
    print(f"{col}의 이상치:\n{outlier_data}\n")

# 원본 데이터에 적용
clean_base_df = base_df[~outliers.any(axis=1)]
print(f"이상치를 제거한 후의 원본 데이터프레임 크기: {clean_base_df.shape}")

 

  • 파생변수로 이상치 여부 컬럼 새로 생성
# IQR 계산 함수 정의
def is_outlier(column):
    q1 = column.quantile(0.25)
    q3 = column.quantile(0.75)
    iqr = q3 - q1
    lower_bound = q1 - (1.5 * iqr)
    upper_bound = q3 + (1.5 * iqr)
    
    # IQR 범위를 벗어난 값은 True로 표시 (이상치)
    return (column < lower_bound) | (column > upper_bound)
    
    
# 각 열에 대해 이상치 여부를 계산하여 새로운 데이터프레임 생성
outlier_flags = numeric_df.apply(is_outlier)

# 이상치 여부를 원본 데이터프레임에 추가
numeric_df['이상치여부'] = outlier_flags.any(axis=1)
numeric_df['이상치여부'].value_counts()

 

 

이상치 처리_빈도 기반: Frequency-Based Method

 
# 컬럼 형식 확인 후 적용을 제외할 컬럼 문자형으로 변환
merged_df[['article_id', 'product_code', 'price']] = merged_df[['article_id', 'product_code', 'price']].astype('object')
merged_df.dtypes

# 특정 형식의 컬럼만 선택하여 리스트
category_df = merged_df.select_dtypes(include = ['int64', 'float']).columns
category_df
# 각 범주형 변수의 고유 값과 빈도수 확인
for col in category_df:
    print(f"\\n{col} 컬럼의 값 분포:")
    print(merged_df[col].value_counts())
# 빈도가 적은 값을 이상치로 간주하는 함수 정의
def low_categories(df, column, threshold=200):
    # 각 값의 빈도 계산
    value_counts = df[column].value_counts()

    # 빈도가 threshold 이하인 값을 찾음
    to_remove = value_counts[value_counts <= threshold].index

    # 해당 값을 NaN으로 대체 (또는 다른 처리를 할 수 있음)
    df[column] = df[column].apply(lambda x: x if x not in to_remove else None)

# 각 범주형 변수에 대해 이상치 제거 적용
for col in category_df:
    low_categories(merged_df, col, threshold=200)  # threshold는 상황에 맞게 조정

# 이상치 제거 후 데이터 확인
print(merged_df.info())
print(merged_df.isna().sum())
# 이상치 제거
merged_df.dropna(inplace = True)
merged_df.isna().sum()

# 이상치 제거 후 빈도수 확인
for col in category_df:
    print(f"\\n{col} 컬럼의 값 분포:")
    print(merged_df[col].value_counts())

 

'[업무 지식] > Python' 카테고리의 다른 글

[Scaler] StandardScaler, MinMaxScaler  (0) 2024.11.22
[2차원 배열 저장] for문  (0) 2024.11.21
[결측값 처리] python 기초  (0) 2024.11.07
[seaborn] 데이터 시각화  (0) 2024.10.31
[matplotlib] 데이터시각화  (0) 2024.10.31