본문 바로가기
데이터분석

프로젝트 데이터셋 선정 및 구조 파악

by 맑은청이 2024. 12. 31.
728x90
반응형

1. 데이터셋 선정 

Kaggle의 'E-Commerce Shipping Data를 선정했다. 

주문 및 배송 관련 데이터를 포함하고 CSV파일로 다운로드가 가능하기 때문이다. 

https://www.kaggle.com/datasets/prachi13/customer-analytics

 

E-Commerce Shipping Data

Product Shipment Delivered on time or not? To Meet E-Commerce Customer Demand

www.kaggle.com

 

2. 구조 파악 코드 예제

import pandas as pd

# 데이터 불러오기
data = pd.read_csv('/Train.csv')  # 파일 경로에 맞게 수정

# 데이터 구조 확인
print(data.info())       # 열 정보 및 데이터 타입
print(data.head())       # 상위 5개 행 출력
print(data.describe())   # 통계 요약
print(data.columns)      # 열 이름 출력
print(data.isnull().sum()) # 결측치 확인

 

  • ID - 고객 ID (고유값).
  • Warehouse block - 제품이 위치한 창고 블록(A, B, C, D, E).
  • Mode of shipment - 배송 수단(Ship, Flight, Road).
  • Customer care calls - 고객센터 문의 횟수.
  • Customer rating - 고객 평점 (1–5).
  • Cost of the product - 제품 가격(USD).
  • Prior purchases - 이전 구매 횟수.
  • Product importance - 제품 중요도(낮음, 중간, 높음).
  • Gender - 고객 성별(남, 여).
  • Discount offered - 제공된 할인율.
  • Weight_in_gms - 제품 무게(그램).
  • Reached on time - 배송 시간 준수 여부(1: 지연, 0: 정시). (Target 변수)

 

data columns (total 12 columns):
 #   Column               Non-Null Count  Dtype 
---  ------               --------------  ----- 
 0   ID                   10999 non-null  int64 
 1   Warehouse_block      10999 non-null  object
 2   Mode_of_Shipment     10999 non-null  object
 3   Customer_care_calls  10999 non-null  int64 
 4   Customer_rating      10999 non-null  int64 
 5   Cost_of_the_Product  10999 non-null  int64 
 6   Prior_purchases      10999 non-null  int64 
 7   Product_importance   10999 non-null  object
 8   Gender               10999 non-null  object
 9   Discount_offered     10999 non-null  int64 
 10  Weight_in_gms        10999 non-null  int64 
 11  Reached.on.Time_Y.N  10999 non-null  int64 
dtypes: int64(8), object(4)

 

 

  ID Warehouse_block Mode_of_Shipment  Customer_care_calls  Customer_rating  \
0   1               D           Flight                    4                2   
1   2               F           Flight                    4                5   
2   3               A           Flight                    2                2   
3   4               B           Flight                    3                3   
4   5               C           Flight                    2                2   

   Cost_of_the_Product  Prior_purchases Product_importance Gender  \
0                  177                3                low      F   
1                  216                2                low      M   
2                  183                4                low      M   
3                  176                4             medium      M   
4                  184                3             medium      F   

   Discount_offered  Weight_in_gms  Reached.on.Time_Y.N  
0                44           1233                    1  
1                59           3088                    1  
2                48           3374                    1  
3                10           1177                    1  
4                46           2484                    1

 

1) 주요 특징 요약

  1. Customer_care_calls (고객센터 문의 횟수)
    • 평균: 약 4회
    • 최소값: 2회
    • 최대값: 7회 (고객 불만 또는 복잡한 주문일 가능성)
    • 분석 포인트: 문의 횟수가 많을수록 배송 지연과의 상관관계 분석 필요.
  2. Customer_rating (고객 평점)
    • 평균: 약 3점
    • 최대값: 5점
    • 최소값: 1점
    • 이상치: 7점(평점 범위를 벗어나는 데이터로 보임 → 정제 필요)
    • 분석 포인트: 낮은 평점이 지연과 관련 있는지 분석 필요.
  3. Cost_of_the_Product (제품 가격)
    • 평균: $210
    • 최소값: $96
    • 최대값: $310
  4. Prior_purchases (이전 구매 횟수)
    • 평균: 약 3.57회
    • 최대값: 10회
    • 최소값: 2회
  5. Discount_offered (할인율)
    • 평균: 13%
    • 최대값: 65%
    • 최소값: 1
  6. Weight_in_gms (제품 무게)
    • 평균: 3634g
    • 최대값: 5050g
    • 최소값: 1001g
    • 분석 포인트: 무거운 제품일수록 지연 위험이 높아질 가능성 검토.
  7. Reached.on.Time_Y.N (배송 시간 준수 여부)
    • 평균 지연 비율: 59.7%
    • 0(정시 도착): 약 40.3%
    • 1(지연 도착): 약 59.7%
    • 분석 포인트: 지연율이 상당히 높으므로, 주요 변수들과의 관계 분석 필요

데이터에 결측값이 없으므로 따로 데이터 전처리를 할 필요는 없다. 

이커머스 데이터인데 날짜가 없는 게 조금 아쉽다.

 

 

 2) 데이터 시각화 

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# 데이터 불러오기
data = pd.read_csv('/Train.csv') 

# 숫자형 열만 선택
numeric_data = data.select_dtypes(include=['number'])

# 상관관계 분석
plt.figure(figsize=(10, 8))
sns.heatmap(numeric_data.corr(), annot=True, cmap="coolwarm", fmt=".2f")
plt.title("Feature Correlation Heatmap")
plt.show()

전체적으로 관계가 강한거 같진 않다. 제일 높은 상관계수가 높은 게 0.6으로 ID와 할인율인데, 할인율에 따라서 한 고객이 다수의 구매로 이끄는 건지 생각할 필요가 있는 거 같다. 다음으로 할인율과 배송시간준수여부가 0.4로 그나마 관계도가 높다.  

 

또한 Weight in gms 즉 물품의 무게 쪽이 상관계수가 다른 컬럼보다 높은 걸 확인할 수 있다. 

 

 

피어슨 상관계수의 범위 

                           

피어슨 상관계수

피어슨 상관계수의 공식은 위와 같다. 보면 양쪽에 차이를 구하고 절댓값을 합치는 듯 수학적 계산을 한다.  

스피어만이나 켄달 타우 같은 다른 상관계수도 있지만 피어슨 상관계수가 선형 관계 분석에 적합하다. 

상관계수 (r)       
관계
0.0 ~ 0.1 거의 또는 전혀 상관 없음
0.1 ~ 0.3 약한 상관관계
0.3 ~ 0.5 보통 수준의 상관관계
0.5 ~ 0.7 강한 상관관계
0.7 ~ 1.0 매우 강한 상관관계
  • 양의 상관관계 (+): 한 변수가 증가하면 다른 변수도 증가.
  • 음의 상관관계 (-): 한 변수가 증가하면 다른 변수는 감소.
  • 0.0 (상관 없음): 두 변수 간 관계가 없음.

보통은 0.5 이상일때부터 의미있는 계수다.

 

# 할인율과 배송 지연 관계
plt.figure(figsize=(8, 6))
sns.boxplot(x='Reached.on.Time_Y.N', y='Discount_offered', data=data)
plt.title('Discount Offered vs On-Time Delivery')
plt.show()

# 제품 무게와 배송 지연 관계
plt.figure(figsize=(8, 6))
sns.boxplot(x='Reached.on.Time_Y.N', y='Weight_in_gms', data=data)
plt.title('Weight vs On-Time Delivery')
plt.show()

 

할인율과 박스플롯

  1. 무게가 가벼운 제품일수록 지연 도착(1)할 가능성이 높은 경향을 보임.
  2. 무거운 제품은 대체로 정시 도착(0)하는 비율이 높음.
  3. 이상치 존재: 특히 정시 도착 그룹에서 가벼운 제품들이 이상치로 나타남.

-> 제품이 무거울 수록 정시 도착이 어려울 거 같은건데 데이터 특성을 확인해보니 의외로 물건이 가벼울수룩 배송 지연 위험이 클 가능성이 높을 거 같다.

# 제품 중요도에 따른 배송 지연
plt.figure(figsize=(8, 6))
sns.countplot(x='Product_importance', hue='Reached.on.Time_Y.N', data=data)
plt.title('Product Importance vs On-Time Delivery')
plt.show()

# 배송 방식에 따른 지연 분석
plt.figure(figsize=(8, 6))
sns.countplot(x='Mode_of_Shipment', hue='Reached.on.Time_Y.N', data=data)
plt.title('Mode of Shipment vs On-Time Delivery')
plt.show()

중요도가 낮을 수록 지연배송이 많이 생긴다. 중요도가 높을 수록 관리가 잘 된다고 파악할 수 있다. 

 

배송방법에 따른 차이

대부분 배를 통한 운송을 하고 있다. 배로 가는 게 지연배송 비율이 낮다. 

 


3. Feature 엔지니어링 

- 결측치 처리

캐글에서 넘어온 데이터 그런지 결측값이 없다. 

만약 데이터에 결측값이 있다면 소수(5%이하)를 삭제 혹은 평균이나 중앙값, 최빈값을 대체한다. 회귀분석을 통해서 대체하는 방식도 있다. 시계열 값에 경우 앞뒤 값으로 채우기도 한다. 

- 이상치 처리

데이터 왜곡 방지를 위해 박스플롯에서 확인된 이상치에 대해서는 z-scroe이나 IQR 방식을 활용하여 처리한다. 

# 이상치 필터링 
Q1 = data['Weight_in_gms'].quantile(0.25)
Q3 = data['Weight_in_gms'].quantile(0.75)
IQR = Q3 - Q1

#범위 1.5배 크기로 확장하여 정상 데이터 확인 
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

*사분위수(Quartiles) 

Q1(제1사분위수) : 하위 25%의 데이터를 포함하는 값 -> 데이터의 25%가 이 값 이하에 위치한다는 뜻이다. 

Q2(제2사분위수) : 50% -> 중앙값(Median) 

Q3(제3사분위수) : 하위 75%

IQR(Interquartile Range): Q3 - Q1 

 -> 데이터의 변동성을 나타내며 이상치를 탐지하는 기준 

 

(추가) z-score(표준 점수)는 데이터가 평균에서 얼마나 떨어져 있는지를 표준편차(Standard Deviation)단위로 나태낸 값이다. 

데이터가 평균과 비교했을 때 상대적인 위치를 알려주는 지표다.

z-scores

z = 0 이면 평균과 동일한 값

z > 0 평균보다 크고 z < 0 이면 평균보다 작다. 

|z| > 2 이면 일반적으로 이상치(oultier)로 간주한다. 

 

여기서 IQR을 사용한 이유는 z-score의 경우에 정규분포일 때 조금 더 정확하기 때문이다. IQR은 비대칭 분포, 즉 왜도 또는 이상값이 많은 경우 유리하다. Weight_in_gms 변수에서 왜도가 나타날 수 있는데 z-score는 이상치를 과소/과대평가할 수있다.  IQR은 데이터의 중앙 값 주변에 집중하기 때문에 극단적인 값에 영향을 덜 받는다. 현실적으로 이커머스 데이터는 득정 제품의 무게나 가격이 극단적으로 클 가능성이 높기 때문에 IQR을 사용한다.  

 

기준 Z-Score IQR
기본 가정 데이터가 정규분포를 따른다고 가정 데이터의 분포 형태에 의존하지 않음
계산 방식 평균(μ)과 표준편차(σ)를 사용 사분위수(Q1, Q3)와 IQR을 사용
이상치 기준 Z > 3 또는 Z < -3 값이 Q1 - 1.5 * IQR 또는 Q3 + 1.5 * IQR 밖에 존재
적합한 상황 정규분포일 때 더 정확함 비대칭 분포(왜도) 또는 이상값이 많은 경우 유리
단점 정규분포가 아니면 부정확할 수 있음 데이터의 극단적인 이상값을 모두 제거할 수 없음

 

lower랑 upper에 1.5를 곱하는 이유는 Empirical Ruel 때문인다. 통계적으로 약 99.7%의 데이터는평균  ± 3σ(표준편차) 안에 존재한다고 한다. 1.5*IQR은 약 ±2.7σ에 해당하므로 이상치를 검출하는 적절한 기준으로 설정한다. 물론 상황마다 적절한 배수의 설정이 필요하다. 

 

- 데이터 변환 및 스케일링 

모델 성능 향상을 위한 데이터 변환 및 스케일링을 수행한다. 

무게나 가격 등의 연속형 변수는 표준화(Standardization) 또는 정규화(Normalization)을 적용한다. 

from sklearn.preprocessing import StandardScaler
#StandardScaler는 표준화를 수행하는 도구 

#객체 생성, 데이터를 평균 0, 표준편차 1로 변환
scaler = StandardScaler() 
data[['Cost_of_the_Product', 'Weight_in_gms']] = scaler.fit_transform(
    data[['Cost_of_the_Product', 'Weight_in_gms']]
)

 

Q. 왜 표준화할까? 

A: 학습 성능 저하 

: 각 변수의 값들이 서로 다른 범위를 가지면 머신러닝 모델의 학습 성능이 저하된다. 

Cost_of_the_Product는 96 ~ 310, Weight_in_gms는 1001~5050으로 범위 차이가 크다. 

경사하강법 기반 모델 최적화, 거리기반 알고리즘 성능향상처럼 많은 모델들이 크기가 큰 변수를 통해 학습 속도가 느려지거나 측정이 어려워진다. 

Cost_of_the_Product Z(Cost_of_the_Product)
200 (200 - μ) / σ
300 (300 - μ) / σ

표준화를 거치면 변수의 범위가 평균 0, 표준편차 1로 조정된다. 이를 통해 편향(Bias)를 방지한다. 

 

범위 말고 두 변수는 범주형 변수가 아닌 (여기서는 Gender, Product_importance가 대상이다) 연속형 변수이기 때문에 표준화를 진행한다. 또 1~5로 범위가 좁다거나 상대적크기만 중요한 경우에는 표준화가 필요하지 안다. 

 

 

- 파생 변수 생성

추가 인사이트를 위한 파생 변수를 생성한다. 

mport numpy as np


# 1. 할인율과 배송 지연 관계 (Discount Offered vs On-Time Delivery)
# 높은 할인율이 지연 위험을 증가시킬 수 있다는 가설 기반 변수 생성

data['High_discount'] = (data['Discount_offered'] > data['Discount_offered'].median()).astype(int)

# 할인율 위험 점수 (할인율 * 중요도 반영)
importance_mapping = {'low': 1, 'medium': 2, 'high': 3}

data['Importance_score'] = data['Product_importance'].map(importance_mapping)
data['Risk_score'] = data['Discount_offered'] * data['Importance_score']


# 2. 제품 무게와 배송 지연 관계 (Weight vs On-Time Delivery)

# 무게 범주화 (경량, 중량, 초중량)
bins_weight = [0, 2000, 4000, 6000]

labels_weight = ['Light', 'Medium', 'Heavy']

data['Weight_category'] = pd.cut(data['Weight_in_gms'], bins=bins_weight, labels=labels_weight)

# 무게당 가격 (Cost_per_gram) 생성

data['Cost_per_gram'] = data['Cost_of_the_Product'] / data['Weight_in_gms']


# 3. 제품 중요도에 따른 배송 지연 (Product Importance vs On-Time Delivery)

# 중요도 점수 활용 (이미 생성됨)

data['High_importance'] = (data['Product_importance'] == 'high').astype(int)


# 4. 배송 방식에 따른 지연 분석 (Mode of Shipment vs On-Time Delivery)
# 배송 방식 더미 변수화
data = pd.get_dummies(data, columns=['Mode_of_Shipment'], drop_first=True)

#  5. 기타 변수 생성

# 고객 문의 횟수 구간화

bins_calls = [0, 3, 6, 10]

labels_calls = ['Low', 'Medium', 'High']

data['Call_frequency'] = pd.cut(data['Customer_care_calls'], bins=bins_calls, labels=labels_calls)

# 반복 구매 여부

data['Repeat_customer'] = (data['Prior_purchases'] > 3).astype(int)

# Weight_category 생성

data['Weight_category'] = pd.cut(data['Weight_in_gms'],

bins=[0, 2000, 4000, 6000],

labels=['Light', 'Medium', 'Heavy'],

include_lowest=True).cat.add_categories(['Unknown']).fillna('Unknown')

# Call_frequency 생성

data['Call_frequency'] = pd.cut(data['Customer_care_calls'],
bins=[0, 3, 6, 10],
labels=['Low', 'Medium', 'High'],
include_lowest=True).cat.add_categories(['Unknown']).fillna('Unknown')

# Call_frequency 숫자형 변환
call_frequency_map = {'Low': 1, 'Medium': 2, 'High': 3}
data['Call_frequency_score'] = data['Call_frequency'].map(call_frequency_map).fillna(0).astype(float)

# 6. 위험도 종합 점수 (배송 지연 위험)

# 위험도 종합 점수 계산 (새로운 변수 추가)

data['Total_risk_score'] = (
data['Risk_score'] +
data['High_discount'] * 2 +      # 높은 할인율 가중치
data['High_importance'] * 3 +   # 높은 중요도 가중치
data['Weight_category'].map({'Light': 1, 'Medium': 2, 'Heavy': 3}).fillna(0) +  # 무게 영향
data['Call_frequency_score'] * 1.5 +  # 고객 문의 빈도 가중치
data['Repeat_customer'] * -2   # 반복 구매 고객 가중치 감소
)

# 결과 확인

print(data.head())

 

1) 평균 할인율 대비 할인율 비율

# 1. 할인율과 배송 지연 관계 (Discount Offered vs On-Time Delivery)
# 높은 할인율이 지연 위험을 증가시킬 수 있다는 가설 기반 변수 생성

data['High_discount'] = (data['Discount_offered'] > data['Discount_offered'].median()).astype(int)

# 할인율 위험 점수 (할인율 * 중요도 반영)
importance_mapping = {'low': 1, 'medium': 2, 'high': 3}

data['Importance_score'] = data['Product_importance'].map(importance_mapping)
data['Risk_score'] = data['Discount_offered'] * data['Importance_score']

 

(할인율)할인율이 높을 수록 배송지연 위험이 증가할 가능성이 있다는 가설을 기반으로 높은 할인율 여부를 나타내는 이진변수를 생성 

 

할인율이 할인율의 중앙값보다 큰 경우 True, 작거나 같으면 False인데 .astype(int)를 통해서 True는 1로 False 0으로 변환한다. 

 

High_discount =1이면 높은 할인율이고 0이면 낮거나 평균인 할인율이다.

 

(중요도)Product_importance를 importance_mapping 에 따라 변환한다. 

 

리스크 점수를 계산하는데 이는 할인율과 중요도를 곱하여 배송지연의 잠재적 리스크점수를 계산하는 거다.

  • Discount_offered = 30, Importance_score = 3 → Risk_score = 90.
  • Discount_offered = 10, Importance_score = 1 → Risk_score = 10.

 

# 4. 배송 방식에 따른 지연 분석 (Mode of Shipment vs On-Time Delivery)
# 배송 방식 더미 변수화

data = pd.get_dummies(data, columns=['Mode_of_Shipment'], drop_first=True)

 

Mode_Of_Shipment(배송 방식) 열을 더미 변수(dummy Variable)로 변환한다. 

범주형 데이터를 머신러닝 모델에서 사용할 수 있도록 숫자형 데이터로 변환하는 방법이다. 

 

*더미 변수화란? 

더미 변수(dummy variable)

범주형 데이터를 0과 1로 구성된 이진 변수로 변환 

각 범주는 별도의 열로 분리되며, 데이터가 해당 범주에 속하면 1, 그렇지 않으면 0이 된다. 

 

원핫코드처럼 해당 컬럼있으면 1로 만들어준다. 

Mode_of_Shipment

Mode_of_Shipment
Ship
Flight
Road
Ship

변환된 데이터:

Mode_of_Shipment_Ship Mode_of_Shipment_Road
1 0
0 0
0 1
1 0
  • Mode_of_Shipment_Ship = 1: 배송 방식이 Ship.
  • Mode_of_Shipment_Road = 1: 배송 방식이 Road.
  • 모두 0: 배송 방식이 Flight.

 

drop_first=True를 사용하는 이유

다중공선성 방지를 유해서다. 

모든 더미 변수를 포함하면 변수 간 상관관계가 발생해 모델 학습에 문제가 될 수 있다. 

Mode_of_Shipment_Ship + Mode_of_Shipment_Road + Mode_of_Shipment_Flight = 1인 경우, 세 변수가 완전히 종속적이기 때문에 첫번째 범주를 제거해 효율성을 높이는 거다. 

 

 

*pd.cut 

연속형 데이터를 특정 구간으로 나누어 범주형 데이터로 변환한다. 

예를 들어 제품 무게가 1500g이면 Light, 3000g이면 Medium으로 범주화한다. 

 

 

.cat.add_categories(['Unknown']).fillna('Unknown')

  • add_categories(['Unknown']):
    • 새로운 카테고리 'Unknown'을 추가하여 나머지 값을 처리 가능.
  • .fillna('Unknown'):
    • **결측값(NaN)**을 'Unknown'으로 대체.
    • 예: Weight_in_gms 값이 비어 있는 경우 해당 값이 'Unknown'으로 처리.
    • .fillna(0)으로 하면 빈값이 0으로 채워짐

 

728x90
반응형