ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 실전 예제 - 온-오프라인 비지니스 분석 09
    빅데이터/Data-Analysis 2022. 3. 1. 12:45

    패스트캠퍼스 '직장인을 위한 파이썬 데이터분석 올인원 패키치 Online' 참조

     

     

     

    • 엑셀은 사람들이 가장 쉽게 접하고 데이터를 쉽게 다를 수 있는 툴이다. 하지만 어느정도 데이터를 다룰 수 있으나 데이터가 점점 방대해지면 한계점이 극명해진다.
    • 각기 다른 데이터 형태, 시각화 등 엑셀에서 하기 정말 어려운 데이터들이 많이 있다. 파이썬은 엑셀의 단점을 완벽히 커버 할 수 있다
    • 이번 강의는 이커머스 데이터와 패스트푸드점 데이터로 진행 될 예정

     

     

     

    01 기본 설정

    • 강의를 진행하기 위해서 두가지 라이브러리를 설치 해 주자
    pip install missingno # 판다스의 데이터 프레임 결측치를 찾는 기능 제공
    pip install squarify # 트리맵 시각화 라이브러리
    • 필요 라이브러리
    # 필수 라이브러리
    import warnings
    warnings.filterwarnings('ignore')
    
    import pandas as pd
    import numpy as np
    
    %matplotlib inline
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import seaborn as sns
    import missingno as msno
    import squarify
    • 데이터 다운 주소
    https://www.kaggle.com/olistbr/brazilian-ecommerce

     

     

    02 하나의 데이터 훑어 보기

    df = pd.read_csv('olist_orders_dataset.csv')
    df.head()

    df.shape
    (99441, 8)
    
    df.columns
    Index(['order_id', 'customer_id', 'order_status', 'order_purchase_timestamp',
           'order_approved_at', 'order_delivered_carrier_date',
           'order_delivered_customer_date', 'order_estimated_delivery_date'],
          dtype='object')
          
    df.info()
     #   Column                         Non-Null Count  Dtype 
    ---  ------                         --------------  ----- 
     0   order_id                       99441 non-null  object
     1   customer_id                    99441 non-null  object
     2   order_status                   99441 non-null  object
     3   order_purchase_timestamp       99441 non-null  object
     4   order_approved_at              99281 non-null  object
     5   order_delivered_carrier_date   97658 non-null  object
     6   order_delivered_customer_date  96476 non-null  object
     7   order_estimated_delivery_date  99441 non-null  object
    • 현재 모든 데이터가 오브젝트 형태이다. 여기서 쓰려고 하는 데이터들은 처리가 되도록 형태를 바꿔줘야 한다
    # 필요한 데이터 타입 변경
    # 필요한 데이터 타입 변경
    df['order_purchase_timestamp'] = pd.to_datetime(df['order_purchase_timestamp'])
    df['order_approved_at'] = pd.to_datetime(df['order_approved_at'])
    df['order_delivered_carrier_date'] = pd.to_datetime(df['order_delivered_carrier_date'])
    df['order_delivered_customer_date'] = pd.to_datetime(df['order_delivered_customer_date'])
    df['order_estimated_delivery_date'] = pd.to_datetime(df['order_estimated_delivery_date'])
    
    df.info()
     3   order_purchase_timestamp       99441 non-null  datetime64[ns]
     4   order_approved_at              99281 non-null  datetime64[ns]
     5   order_delivered_carrier_date   97658 non-null  datetime64[ns]
     6   order_delivered_customer_date  96476 non-null  datetime64[ns]
     7   order_estimated_delivery_date  99441 non-null  datetime64[ns]

     

     

     

     

     

    03 EDA

    df.isnull().sum()
    order_id                            0
    customer_id                         0
    order_status                        0
    order_purchase_timestamp            0
    order_approved_at                 160
    order_delivered_carrier_date     1783
    order_delivered_customer_date    2965
    order_estimated_delivery_date       0
    # 결측치가 있는 row 출력
    df_null = df[df.isnull().any(axis=1)]
    df_null
    
    2980 rows × 8 columns
    
    # 특정 컬럼에 null 출력
    df_col1 = df[df['order_approved_at'].isnull()]
    df_col1

     

     

    ▶ 결측치 제거

    • 크게 삭제하거나 특정값으로 채우는 방법이 있다
    • 데이터가 많을 경우는 삭제가 가장 베스트지이지만 그렇지 않은 경우는 하나하나의 데이터가 소중하기때문에 특정값으로 대체하게됨
    # 결측치 처리
    # axis=0 로우, 1= 컬럼
    df_clean = df.dropna(axis=0)
    
    # 인덱싱 재지정
    df_clean.reset_index(drop=True, inplace=True)
    df_clean

     

     

    ▶ 한 컬럼 분석

    # 한 컬럼 분석
    df_clean['order_status'].unique()
    array(['delivered', 'canceled'], dtype=object)
    
    # 주문 상태 개수
    df_clean['order_status'].value_counts()
    delivered    96455
    canceled         6
    
    # 주문 상태 널값 체크
    df_null['order_status'].unique()
    array(['invoiced', 'shipped', 'processing', 'unavailable', 'canceled',
           'delivered', 'created', 'approved'], dtype=object)
           
    df_null['order_status'].value_counts()
    shipped        1107
    canceled        619
    unavailable     609
    invoiced        314
    processing      301
    delivered        23
    created           5
    approved          2
    • 이렇게 보면 이해하기가 편하지 않다. 주문 상태의 취소와 배송중 취소건을 시각화 해 보자
    # 취소건 시각화
    A = df_clean[df_clean['order_status'] == 'canceled'].shape[0] #취소건 (6,8) 중 6개
    B = df_null[df_null['order_status']== 'canceled'].shape[0] #취소건 (619,8) 중 619개
    
    temp = pd.DataFrame(columns=['del_finished', 'del_not_finished'],
                       index=['cancel_cnt'])
    
    # temp에 값 넣기
    temp.loc['cancel_cnt', 'del_finished'] = A
    temp.loc['cancel_cnt', 'del_not_finished'] = B
    temp
    
    	del_finished	del_not_finished
    cancel_cnt	6	619

    ▶ 막대 그래프로 표현

    • 차트를 그릴시에 x,y축에 무엇을 넣느냐에따라 방향을 바꿔 줄 수 있다
    temp.T

    데이터 프레임 축이 변경 된것을 볼 수 있다.

     

     

     

    04 결측치를 다른 데이터로 채우기

    • 현재 'olist_orders_dataset' 테이블에 있는 컬럼들 중 4가지 컬럼은 아래와 같다
      order_purchase_timestamp - 구매 시작 날짜/시간
      order_approved_at - 결제 완료 날짜/시간
      order_delivered_customer_date - 실제 고객한테 배달 완료된 날짜/시간
      order_estimated_delivery_date - 시스템에서 고객한테 표시되는 예상배달날짜
    • 이 컬럼들을 가지고 새로운 정보들을 채울 예정
      order_approved_at - order_purchase_timestamp : pay_lead_time(단위: 분) - 고객이 구매 후 결제까지의 시간
      order_delivered_customer_date - order_approved_at : delivery_lead_time(단위: 일) - 배달이 걸리는 시간
      order_estimated_delivery_date - order_delivered_customer_date : estimated_date_miss(단위: 일) - 실제로 걸린 시간 (즉 배송 예측 시간과 실제 시간의 차를 보여줌)

     

    # 고객이 구매 후 결제까지의 시간
    df_clean['pay_lead_time'] = df_clean['order_approved_at'] - df_clean['order_purchase_timestamp']
    
    # 추후 계산이 용이하도록 분단위로 변경
    df_clean['pay_lead_time_m'] = df_clean['pay_lead_time'].astype('timedelta64[m]')
    
    # 배달 리드 타임
    df_clean['delivery_lead_time'] = df_clean['order_delivered_customer_date'] - df_clean['order_approved_at']
    
    # 일 단위로 변경
    df_clean['delivery_lead_time_d'] = df_clean['delivery_lead_time'].astype('timedelta64[D]')
    
    # 예상날짜 차이
    # 배달 리드 타임
    df_clean['estimated_date_miss'] = df_clean['order_estimated_delivery_date'] - df_clean['order_delivered_customer_date']
    
    # 일 단위로 변경
    df_clean['estimated_date_miss_d'] = df_clean['estimated_date_miss'].astype('timedelta64[D]')
    
    # 세 컬럼 모두 정수형으로 통일
    df_clean['pay_lead_time_m'] = df_clean['pay_lead_time_m'].astype(int)
    df_clean['delivery_lead_time_d'] = df_clean['delivery_lead_time_d'].astype(int)
    df_clean['estimated_date_miss_d'] = df_clean['estimated_date_miss_d'].astype(int)
    
    order_id                                  object
    customer_id                               object
    order_status                              object
    order_purchase_timestamp          datetime64[ns]
    order_approved_at                 datetime64[ns]
    order_delivered_carrier_date      datetime64[ns]
    order_delivered_customer_date     datetime64[ns]
    order_estimated_delivery_date     datetime64[ns]
    pay_lead_time                    timedelta64[ns]
    pay_lead_time_m                            int32
    delivery_lead_time               timedelta64[ns]
    delivery_lead_time_d                       int32
    estimated_date_miss              timedelta64[ns]
    estimated_date_miss_d                      int32

     

     

    ▶ 시각화

    # 새로운 데이터로 시각화
    
    # 결제까지 걸리는 시간
    plt.figure(figsize=(12,6))
    sns.distplot(df_clean['pay_lead_time_m'])
    plt.show()

    결제 리드 타임은 대부분 짧은 시간안에 이루어지고 전문 용어로 'right skewed'로 오른쪽으로 치우쳤다고 한다. 

    # 배달까지 걸리는 시간
    plt.figure(figsize=(12,6))
    sns.distplot(df_clean['delivery_lead_time_d'])

    똑같이 오른쪽으로 치우쳐져있다.

    # 실제 걸리는 시간
    plt.figure(figsize=(12,6))
    sns.distplot(df_clean['estimated_date_miss_d'])

    양/음수값을 모두 가지고 있고 음수는 시스템에서 알려준날짜보다 더 빨리 도착했다고 예상 할 수 있다

    ▶ 다시 이 컬럼들을 요약을 해보면

    # 요약
    df_clean[['pay_lead_time_m', 'delivery_lead_time_d','estimated_date_miss_d']].describe()

    • 결제까지는 616분, 배달완료는 11일, 시스템 예상 날짜 차이는 10일 정도 
    • 시스템 예상 날짜는 조금 줄여도 될 듯
    • max값을 보면 재미있는데 결제까지 44486분이 걸린경우는 장바구니에 넣어놓고 진행을 안했다거나, 208일의 배달이 걸린 데이터를 보는등 재미있는요소들이 많다
    • 또한 배달 시간에 -7 이 있는데 이는 잘못된 데이터일 가능성이 높다. (배달전에 이미 배달됬다고 찍힌 경우)
    # 이상한 데이터 체크
    df_clean[df_clean['delivery_lead_time_d']==-7]

    결제를 9월13일에 했는데 배달이 9월6일로 찍혀있다. 잘못된 데이터!

    ▶ 이렇게 데이터 양이 많을 때 쉽게 어떠한 오류(이상치) 데이터들이 있는지 보는 방법이 'BoxPlot'. 데이터가 많은 경우 박스를 각각 찍어주는것이 좋다. 

    # 각각 찍어서 보자
    plt.figure(figsize=(12,6))
    sns.boxplot(data=df_order_time['pay_lead_time_m'], color='red');
    
    # 각각 찍어서 보자
    plt.figure(figsize=(12,6))
    sns.boxplot(data=df_order_time['delivery_lead_time_d'], color='yellow');
    
    # 각각 찍어서 보자
    plt.figure(figsize=(12,6))
    sns.boxplot(data=df_order_time['estimated_date_miss_d'], color='green');

    • 이상치들이 박스 바깥에 많이 몰려있다

    ▶ 이상치들을 걸러 내자

    즉 박스 위아래로 벗어나는 데이터들을 구하는 것

    # 이상치 검출 코드
    def outliers_iqr(data): 
        q1, q3 = np.percentile(data, [25, 75]) #박스플롯의 Q1,Q3 영역 설정
        iqr = q3 - q1 # Q1-Q3 사이의 범위
        lower_bound = q1 - (iqr * 1.5) # 그 범위보다 작거나
        upper_bound = q3 + (iqr * 1.5) # 그 범위보다 크거나
        
        return np.where((data > upper_bound)|(data < lower_bound))
    # pay_lead_time_m 이상치
    outliers_iqr(df_order_time['pay_lead_time_m'])[0].shape[0]
    8915
    
    # delivery_lead_time_d 이상치
    outliers_iqr(df_order_time['delivery_lead_time_d'])[0].shape[0]
    4772
    
    # estimated_date_miss_d 이상치
    outliers_iqr(df_order_time['estimated_date_miss_d'])[0].shape[0]
    4300
    # 각각의 이상치 해당값 출력
    df_order_time.loc[pay_lead_outlier_index, 'pay_lead_time_m']
    
    # 각각의 이상치 해당값 출력
    df_order_time.loc[del_lead_outlier_index, 'delivery_lead_time_d']
    
    # 각각의 이상치 해당값 출력
    df_order_time.loc[est_lead_outlier_index, 'estimated_date_miss_d']

    ▶ 이 이상치를 제거

    # 이상치에 대한 세 컬럼을 합치기
    lead_outlier_index = np.concatenate((pay_lead_outlier_index,
                                        del_lead_outlier_index,
                                        est_lead_outlier_index), axis=None)
    
    print(len(lead_outlier_index))
    lead_outlier_index
    
    # for문을 통해 이상치가 아닌 리드타임 값의 인덱스를 추리기
    lead_not_outlier_index = []
    
    for i in df_order_time.index:
        #lead_outlier_index 에 포함 되지 않으면 추가
        if i not in lead_outlier_index:
            lead_not_outlier_index.append(i)
            
    print(lead_not_outlier_index[:20])
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16, 17, 18, 20, 21, 22]
    
    
    # 이상치가 아닌 값들 출력
    df_order_time_clean = df_order_time.loc[lead_not_outlier_index]
    df_order_time_clean
    
    # 인덱스를 다시 재 정렬
    df_order_time_clean = df_order_time_clean.reset_index(drop=True)
    df_order_time_clean

    이상치를 제거하고 인덱싱을 제정렬 하지 않으면 같은 데이터 개수이지만 인덱싱이 정렬되지않은 데이터가 그대로 꼽히게 된다. 반드시 인덱싱을 해줘야한다.&nbsp;

     

    ▶ 다시 요약

    이상치 제거 후 vs 제거 전

    • 이상치를 제거하니 데이터가 더 깔끔하고 작은 수치들로 변화 되었다. 
Designed by Tistory.