[TIL] [심화 프로젝트] 5일차: 데이터 전처리

Donghyun·2024년 9월 6일
0

TIL (Today I Learned)

목록 보기
44/53

✅ 오늘의 목표

  • 데이터 전처리(스케일링, 범주형 변수 인코딩)

1. 변수 스케일링

1-1. 수치형 변수 스케일링

현재 수치형 변수가 오른쪽으로 꼬리가 긴 right-skewed 분포를 가지고 있다. 따라서, 로그변환 방법으로 변수들을 조금 더 정규분포에 더 가깝게 만들어 비대칭성을 줄인다.

이후 정규화(Normalization)을 통해 데이터의 범위를 일정한 구간으로 변환하여 모델의 학습 속도와 성능을 높이고자 한다.

  • KNN과 같은 거리 기반 알고리즘에서는 변수 간의 값의 범위가 일치하지 않으면 거리 계산에서 문제가 발생할 수 있는데, 정규화를 통해 해결 가능.

'product_length_cm', 'product_height_cm', 'product_width_cm'

세 개의 컬럼을 곱해서 부피 컬럼을 만들고 기존 3 개의 컬럼은 드랍.

# 'product_length_cm', 'product_height_cm', 'product_width_cm' 곱해서 'volume' 컬럼 생성
merged_df_cleaned['volume'] = merged_df_cleaned['product_length_cm'] * merged_df_cleaned['product_height_cm'] * merged_df_cleaned['product_width_cm']

# 기존의 세 개의 컬럼 드랍
merged_df_cleaned = merged_df_cleaned.drop(columns=['product_length_cm', 'product_height_cm', 'product_width_cm'])

이후에 팀원들과 회의를 통해 수치형, 범주형 변수를 재정의 해줬다.

numerical_cols = ['price', 'shipping_charges', 'payment_value', 'product_weight_g', 'volume']
categorical_cols = ['order_status','customer_zip_code_prefix', 'customer_city', 'customer_state',
                    'order_item_id', 'payment_type', 'payment_installments', 'product_category_name']

수치형 변수 로그변환 및 정규화 수행 코드

# 수치형 변수 로그변환 함수

def log_transform(df, cols):
    df_log_transformed = df[cols].apply(lambda x: np.log1p(x))
    
    return df_log_transformed

def normalize(df, cols):
    scaler = MinMaxScaler()
    df_normalized = pd.DataFrame(scaler.fit_transform(df[cols]), columns=cols, index=df.index)
    
    return df_normalized

# 로그 변환
log_transformed_df = log_transform(merged_df_cleaned, numerical_cols)

# 정규화 수행
normalized_df = normalize(log_transformed_df, numerical_cols)

# 결과를 원래 데이터프레임에 반영
merged_df_cleaned[numerical_cols] = normalized_df

스케일링 후 결과 확인

# 변수 스케일링 후 수치형 kdeplot()

plt.figure(figsize=(20, 10))

for idx, col in enumerate(numerical_cols):
    plt.subplot(3, 4, idx+1)
    ax = sns.kdeplot(merged_df_cleaned[col], shade=True)
    plt.title(f'KDE Plot of {col}')

plt.tight_layout()  
plt.show()
  • 결과:

2. 범주형 변수 인코딩

3-4. 범주형 변수 인코딩

One-Hot-Encoding : 각 카테고리를 0과 1로 구성된 벡터로 표현하는 기법. 카테고리의 수만큼 벡터가 생성되므로 각 카테고리가 새로운 변수가 되어 표현된다. 다만, 카테고리가 너무 많은 변수의 경우 데이터의 cardinality를 증가시켜 모델의 성능을 저하시킬 수 있다는 단점.

Label-Encoding : n개의 범주형 데이터를 0 ~ n-1의 연속적인 수치 데이터로 표현. 데이터의 범주가 3개 이상일 때는 주의해서 사용. 라벨 인코딩은 한번 실행시킬 때 단 하나의 컬럼만 실행 가능.

우리 데이터에는 범주형 변수가 많고, 예를들어 product_category_name에는 70개의 카테고리를 가지고 있기 때문에, 원-핫 인코딩과 라벨 인코딩을 사용하는 데는 한계가 있을 것으로 사료된다. 따라서, 다음의 두 가지를 고려한 인코딩 방법을 생각해야 할 것이다.

  • 카디널리티 문제 : 원-핫 인코딩의 경우, 카테고리의 수가 많을수록 차원이 증가하여 데이터가 희소해지고, 계산 비용이 높아지며, 모델 성능이 저하될 수 있다.
  • 거리 측정 문제 : 라벨 인코딩은 범주형 데이터를 순서가 있는 수치 데이터로 변환하기 때문에, KNN과 같은 거리 기반 모델에서는 인코딩된 값 간의 인위적인 거리 차이가 생겨 잘못된 결과를 초래할 수 있다.

우리의 선택 : Label-Encoding

  • 범주형 데이터가 많기는 하지만, 그 카테고리를 줄인 후 레이블 인코딩을 하고자 한다.

추가적으로

  • customer_zip_code_prefix, customer_city, customer_state와 같이 위치 정보를 나타내는 컬럼들은 따로 빼서 5개의 지방으로 묶는 과정이 필요.
  • 장난감도 카테고리가 70개나 되기 때문에 묶어서 줄이는 과정이 필요.
  • 날짜 변수들 order_purchase_timestamp, order_approved_at, order_delivered_timestamp, order_estimated_delivery_date은 dtype을 datetime으로 변경해 향후 분석에 더욱 용이하게 사용할 수 있도록 한다.
  • order_status, payment_type 레이블 인코딩

customer_state, customer_zip_code_prefix, customer_city

region 맵핑:

  • 남동부 지방 : 0
  • 남부 지방 : 1
  • 북동부 지방 : 2
  • 북부 지방 : 3
  • 중서부 지방 : 4
# state로 5개의 지방으로 묶기

state_to_region = {
    'AC': '북부 지방', 'AL': '북동부 지방', 'AP': '북부 지방', 'AM': '북부 지방',
    'RR': '북부 지방', 'RO': '북부 지방', 'PA': '북부 지방', 'PB': '북동부 지방',
    'MA': '북동부 지방', 'PI': '북동부 지방', 'PE': '북동부 지방', 'RN': '북동부 지방',
    'CE': '북동부 지방', 'SE': '북동부 지방', 'BA': '북동부 지방', 'DF': '중서부 지방',
    'TO': '북부 지방', 'GO': '중서부 지방', 'MS': '중서부 지방', 'MT': '중서부 지방',
    'RJ': '남동부 지방', 'SP': '남동부 지방', 'MG': '남동부 지방', 'ES': '남동부 지방',
    'RS': '남부 지방', 'SC': '남부 지방', 'PR': '남부 지방'
}

# state 컬럼을 기준으로 지방으로 변환
merged_df_cleaned['region'] = merged_df_cleaned['customer_state'].map(state_to_region)

# customer_zip_code_prefix, customer_city, customer_state 컬럼 drop
columns_to_drop = ['customer_zip_code_prefix', 'customer_city', 'customer_state']
merged_df_cleaned = merged_df_cleaned.drop(columns=columns_to_drop)
merged_df_cleaned['region'].value_counts()
  • 결과:
    region
    남동부 지방    55872
    남부 지방     11141
    북동부 지방     5471
    중서부 지방     4463
    북부 지방       924
    Name: count, dtype: int64
# 결과 확인했으니 이제 레이블 인코딩

label_encoder = LabelEncoder()

# 'region' 컬럼을 레이블 인코딩
merged_df_cleaned['region'] = label_encoder.fit_transform(merged_df_cleaned['region'])
merged_df_cleaned['region'].value_counts()
  • 결과:
    region
    0    55872
    1    11141
    2     5471
    4     4463
    3      924
    Name: count, dtype: int64

product_category_name

팀원들과 회의를 통해 총 카테고리를 7개로 나눠 기존의 70개의 카테고리를 줄여줬다.

product_category_group 인코딩:

  • 가구/인테리어: 0
  • 건설/공구: 1
  • 문구/사무용품: 2
  • 생활용품: 3
  • 장난감: 4
  • 전자제품: 5
  • 패션: 6
# product_category_name 종류별로 묶어서 카테고리 줄이기

category_mapping = {
    '가구/인테리어': [
        'furniture_decor', 'furniture_living_room', 'furniture_bedroom', 
        'furniture_mattress_and_upholstery', 'kitchen_dining_laundry_garden_furniture', 
        'la_cuisine', 'flowers', 'cool_stuff', 'perfumery', 'party_supplies', 
        'bed_bath_table', 'market_place', 'home_construction', 'christmas_supplies'
    ],
    '패션': [
        'fashion_underwear_beach', 'fashion_bags_accessories', 'fashion_shoes', 
        'fashion_male_clothing', 'fashion_sport', 'fashion_childrens_clothes', 
        'fashio_female_clothing', 'housewares', 'watches_gifts'
    ],
    '전자제품': [
        'telephony', 'computers_accessories', 'audio', 'tablets_printing_image', 
        'cine_photo', 'musical_instruments', 'consoles_games', 'dvds_blu_ray', 
        'music', 'electronics', 'air_conditioning', 'small_appliances', 
        'home_appliances', 'home_appliances_2', 'small_appliances_home_oven_and_coffee', 
        'home_comfort_2', 'signaling_and_security', 'security_and_services', 
        'fixed_telephony'
    ],
    '건설/공구': [
        'construction_tools_construction', 'costruction_tools_garden', 
        'construction_tools_safety', 'construction_tools_lights', 
        'costruction_tools_tools', 'garden_tools'
    ],
    '생활용품': [
        'baby', 'diapers_and_hygiene', 'health_beauty', 'home_confort', 
        'luggage_accessories', 'auto', 'food', 'drinks', 'food_drink', 
        'sports_leisure', 'pet_shop', 'agro_industry_and_commerce'
    ],
    '문구/사무용품': [
        'stationery', 'office_furniture', 'books_technical', 
        'books_general_interest', 'books_imported', 'arts_and_craftmanship', 
        'art', 'industry_commerce_and_business'
    ],
    '장난감': ['toys']
}

# 카테고리 매핑을 수행하는 함수
def map_category(category_name):
    for main_category, subcategories in category_mapping.items():
        if category_name in subcategories:
            return main_category
    return '기타'  

# `product_category_name` 컬럼을 매핑하여 새로운 컬럼 추가
merged_df_cleaned['product_category_group'] = merged_df_cleaned['product_category_name'].apply(map_category)

# 'product_category_name' 컬럼 삭제
merged_df_cleaned = merged_df_cleaned.drop(columns='product_category_name')

merged_df_cleaned['product_category_group'].value_counts()
  • 결과:
    product_category_group
    장난감        58189
    생활용품        6108
    가구/인테리어     4968
    전자제품        4164
    패션          2730
    건설/공구       1107
    문구/사무용품      605
    Name: count, dtype: int64
# product_category_name 잘 맵핑 됐으니 이제 레이블 인코딩

label_encoder = LabelEncoder()

# 'product_category_group' 컬럼을 레이블 인코딩
merged_df_cleaned['product_category_group'] = label_encoder.fit_transform(merged_df_cleaned['product_category_group'])

# 변환된 데이터 확인
merged_df_cleaned['product_category_group'].value_counts()
  • 결과:
    product_category_group
    4    58189
    3     6108
    0     4968
    5     4164
    6     2730
    1     1107
    2      605
    Name: count, dtype: int64

order_purchase_timestamp, order_approved_at, order_delivered_timestamp, order_estimated_delivery_date

# 범주형 변수들 처리 전 날짜 변수들의 dtype datetime으로 변경.
date_columns = ['order_purchase_timestamp', 'order_approved_at', 
                'order_delivered_timestamp', 'order_estimated_delivery_date']

for col in date_columns:
    merged_df_cleaned[col] = pd.to_datetime(merged_df_cleaned[col])

merged_df_cleaned[date_columns].dtypes
  • 결과:
    order_purchase_timestamp         datetime64[ns]
    order_approved_at                datetime64[ns]
    order_delivered_timestamp        datetime64[ns]
    order_estimated_delivery_date    datetime64[ns]
    dtype: object

order_status, payment_type

order_status:

  • 'delivered': 0,
    'shipped': 1,
    'canceled': 2,
    'unavailable': 3,
    'processing': 4,
    'invoiced': 5,
    'created': 6,
    'approved': 7

payment_type:

  • 'credit_card': 0,
    'wallet': 1,
    'voucher': 2,
    'debit_card': 3,
    'not_defined': 4
# order_status, payment_type 인코딩

# 매핑 딕셔너리 정의
order_status_mapping = {
    'delivered': 0,
    'shipped': 1,
    'canceled': 2,
    'unavailable': 3,
    'processing': 4,
    'invoiced': 5,
    'created': 6,
    'approved': 7
}

payment_type_mapping = {
    'credit_card': 0,
    'wallet': 1,
    'voucher': 2,
    'debit_card': 3,
    'not_defined': 4
}

# 직접 레이블 인코딩 수행
merged_df_cleaned['order_status_encoded'] = merged_df_cleaned['order_status'].map(order_status_mapping)
merged_df_cleaned['payment_type_encoded'] = merged_df_cleaned['payment_type'].map(payment_type_mapping)

# 원본 컬럼 제거 (선택 사항)
merged_df_cleaned = merged_df_cleaned.drop(columns=['order_status', 'payment_type'])

모든 데이터 전처리 후의 결과:

merged_df_cleaned.to_csv('merged_df_pre_processed.csv', index=False)

merged_df_cleaned.info()
  • 결과:
    <class 'pandas.core.frame.DataFrame'>
    Index: 77871 entries, 0 to 119159
    Data columns (total 19 columns):
     #   Column                         Non-Null Count  Dtype         
    ---  ------                         --------------  -----         
     0   order_id                       77871 non-null  object        
     1   customer_id                    77871 non-null  object        
     2   order_purchase_timestamp       77871 non-null  datetime64[ns]
     3   order_approved_at              77871 non-null  datetime64[ns]
     4   order_delivered_timestamp      76297 non-null  datetime64[ns]
     5   order_estimated_delivery_date  77871 non-null  datetime64[ns]
     6   order_item_id                  77871 non-null  int64         
     7   product_id                     77871 non-null  object        
     8   seller_id                      77871 non-null  object        
     9   price                          77871 non-null  float64       
     10  shipping_charges               77871 non-null  float64       
     11  payment_installments           77871 non-null  float64       
     12  payment_value                  77871 non-null  float64       
     13  product_weight_g               77871 non-null  float64       
     14  volume                         77871 non-null  float64       
     15  region                         77871 non-null  int64         
     16  product_category_group         77871 non-null  int64         
     17  order_status_encoded           77871 non-null  int64         
     18  payment_type_encoded           77871 non-null  int64         
    dtypes: datetime64[ns](4), float64(6), int64(5), object(4)
    memory usage: 11.9+ MB

기존 데이터 119,160개 → 전처리 후 77,871

기존 컬럼 24개 → 전처리 후 19


내일 할 일

  • 가설 세우기.
  • 그 가설을 검정하기 위한 다양한 클러스터링 알고리즘 적용
  • 최적의 군집수 결정
  • 모델 성능 비교(엘보우, 실루엣)
profile
데이터분석 공부 일기~!

0개의 댓글