6 분 소요

🚩 Chapter 03


목차(Context)

  • 01.Data Mart 기획 및 설계
  • 02.Data 추출 및 Mart 개발
  • 03.Integer Feature Engineering
  • 04.Categoical Feature Engineering

01. Data Mart 기획 및 설계


모델링을 위한 Data Mart 기획

image

  • Sampling 완료 후 월 별 기준이 되는 CustomerID를 추출한 상태
  • Mart를 구성하기 위한 준비가 모두 완료된 상태
  • 가설을 수립하고, 가설에 해당하는 다양한 변수 Category와 List를 생성
  • 풍부한 Data Mart를 기획해야 좋은 모델의 성능과 다양한 해석이 가능

image

02. Data 추출 및 Mart 개발


Data Mart 기획서 데이터 추출

  • 상위 단계에서 정의한 Data Mart를 기반으로 변수를 추출하는 과정
  • 변수 추출은 Sampling한 Data로 진행
# ▶ 최초 Data에서 bsym(기준년월)을 추가한 origin data
df_origin.head()
  • 아까 복사 해놨던 원본에 bsym붙인거

└ Mart 개발 준비

# ▶ Sampling한 CustomerID 대상 Mart 개발 수행
df_all_sample.head()
  • 샘플링 한게 맞는지 확인? .shape
# ▶ 원본(Origin) data에서 Sampling 하기 위한 key 생성 (1)
df_origin['sample_key'] = df_origin['bsym'].astype(str) + df_origin['CustomerID'].astype(str)
df_origin.head()
  • 원본에 키 형성
# ▶ 원본(Origin)에서 Sampling 하기 위한 key 생성 (2)
df_all_sample['sample_key'] = df_all_sample['bsym'].astype(str) + df_all_sample['CustomerID'].astype(str)
df_all_sample.head()
  • 샘플링한 데이터에도 키 형성
# ▶ sampling 된 CustomerID만 추출 
df_origin_sample = df_origin[df_origin['sample_key'].isin(df_all_sample['sample_key'])]

# ▶ amt col 생성
df_origin_sample['amt'] = df_origin_sample['UnitPrice'] * df_origin_sample['Quantity']
df_origin_sample.head()
  • isin: 원본에서 샘플에 있는 키만 가지고 옴
  • amt: 양 X 단위 가격
df_origin.shape, df_origin_sample.shape
  • 39만->11만

└ Mart 추출 시작

# ▶ 변수 생성 시작 (total_amt)
df_mart = df_origin_sample.groupby(['bsym', 'CustomerID'])['amt'].agg(total_amt = ('sum')).reset_index()
df_mart
  • total_amt = (‘sum’): sum 해준 뒤, total_amt로 열 만들어줌 유용
  • reset_index는 써주는게 유용
# ▶ 변수 생성 시작 (max_amt, min_amt)
# pd.DataFrame(df_origin_sample.groupby(['bsym', 'CustomerID', 'InvoiceNo'])['amt'].agg(amt = ('sum')).reset_index()).groupby(['bsym', 'CustomerID'])['amt'].agg(max_amt = ('max')).reset_index()
df_mart['max_amt', 'min_amt'](https://code7ssage.github.io/'max_amt', 'min_amt'//) = pd.DataFrame(df_origin_sample.groupby(['bsym', 'CustomerID', 'InvoiceNo'])['amt'].agg(amt = ('sum')).reset_index()).groupby(['bsym', 'CustomerID'])['amt'].agg(max_amt = ('max'), min_amt = ('min')).reset_index()['max_amt', 'min_amt'](https://code7ssage.github.io/'max_amt', 'min_amt'//)
df_mart
  • bsym, customer ID, invoiceNo 기준으로 그룹화 -> amt=sum -> reset
  • sym, customer ID 그룹화 -> max, min -> reset ->max, min만 가져옴(대괄호 2개)
# ▶ 변수 생성 시작 (total_cnt)
df_mart['total_cnt'] = pd.DataFrame(df_origin_sample.groupby(['bsym', 'CustomerID'])['InvoiceNo'].agg(total_cnt = ('nunique'))).reset_index()['total_cnt']
df_mart
  • nunique: unique한 애들만 카운트
# ▶ 변수 생성 시작 (max_cnt, min_cnt)
df_mart['max_cnt', 'min_cnt'](https://code7ssage.github.io/'max_cnt', 'min_cnt'//) = pd.DataFrame(df_origin_sample.groupby(['bsym', 'CustomerID', 'InvoiceNo'])['StockCode'].agg(stock_cnt = 'nunique').reset_index()).groupby(['bsym', 'CustomerID'])['stock_cnt'].agg(max_cnt = ('max'), min_cnt = ('min')).reset_index()['max_cnt', 'min_cnt'](https://code7ssage.github.io/'max_cnt', 'min_cnt'//)
df_mart
# ▶ 변수 생성 시작 (total_qty, max_qty, min_qty)
df_mart['total_qty', 'max_qty', 'min_qty'](https://code7ssage.github.io/'total_qty', 'max_qty', 'min_qty'//) = df_origin_sample.groupby(['bsym', 'CustomerID'])['Quantity'].agg(total_qty = 'sum', max_qty = 'max', min_qty = 'min').reset_index()['total_qty', 'max_qty', 'min_qty'](https://code7ssage.github.io/'total_qty', 'max_qty', 'min_qty'//)
df_mart
# ▶ 변수 생성 시작 (country)
df_mart['Country'] = df_origin_sample.groupby(['bsym', 'CustomerID'])['Country'].agg(country = 'first').reset_index()['country']
df_mart
# ▶ target join
df_mart = pd.merge(df_mart, df_all_sample['bsym', 'CustomerID', 'target'](https://code7ssage.github.io/'bsym', 'CustomerID', 'target'//), how='left', on=['bsym', 'CustomerID'])
df_mart
  • target 데이터가 없으니 all sample에서 가져옴, 기준: bsym, customer ID
# ▶ 최종 Data set
df_mart.shape

03. Integer Feature Engineering


Feature Engineering이란?

  • 모델링의 목적인 성능향상을 위해 Feature를 생성, 선택, 가공하는 일련의 모든 활동

Target 변수와의 의미있는 변수 선택하는 방법

  • Regression(회귀) : 상관계수 분석을 통해 유의미한 변수 선택
  • Classification(분류) : bin(통)으로 구분 후 Target 변수와의 관계 파악

IV (Information Value)

  • Feature 하나가 Good(Target)과 Bad(Non-target)을 잘 구분해줄 수 있는지에 대한 정보량을 표현하는 방법론
  • IV 수치가 클수록 Target과 Non-target을 잘 구분할 수 있는 정보량이 많은 Feature이고, IV 수치가 작을수록 정보량이 적은 Feature
  • (Target data 구성비 - Non-target data 구성비) * WoE
  • WoE(Weight of Evidence) = ln(Target data 구성비 / Non-target data 구성비)
  • ln(자연로그)를 취하는 이유? : WoE의 최대, 최소값의 범위를 맞춰주기 위해 (1~∞ , -∞~1)

개념 참조

└ Regression 문제 (corr)

df_mart.head()
# ▶ heatmap plot
import seaborn as sns
# ▶ 모든 조합, 상관계수 표현

df_pair = df_mart['total_amt', 'max_amt', 'min_amt', 'total_cnt','max_cnt', 'min_cnt', 'total_qty', 'max_qty', 'min_qty'](https://code7ssage.github.io/'total_amt', 'max_amt', 'min_amt', 'total_cnt','max_cnt', 'min_cnt', 'total_qty', 'max_qty', 'min_qty'//)
mask = np.triu(np.ones_like(df_pair.corr(), dtype=np.bool))
sns.heatmap(df_pair.corr(), vmin = -1, vmax = +1, annot = True, cmap = 'coolwarm', mask = mask);
plt.gcf().set_size_inches(10, 10)
  • mask: heatmap에서 중복되는 대칭인 부분 가려주는거

image

└ Classification 문제 (IV)

# ▶ Binning
total_amt_quantile  = df_mart['total_amt'].quantile(q=list(np.arange(0.05,1,0.05)), interpolation = 'nearest')
# total_amt_quantile

# ▶ 그룹 초기화 
df_mart['total_amt_grp'] = 0

# ▶ 40%, 75% 기준으로 총 3개 구간화 진행 
for i in  range(len(df_mart['total_amt'])) :
  if df_mart.loc[i, "total_amt"] <= total_amt_quantile.iloc[7] : # 40%
    df_mart.loc[i, 'total_amt_grp'] = 1
  elif df_mart.loc[i, "total_amt"] <= total_amt_quantile.iloc[14] : # 75%
    df_mart.loc[i, 'total_amt_grp'] = 2
  else : df_mart.loc[i, 'total_amt_grp'] = 3
  • quantile: 5%씩 잘라서 데이터 리스트 만들어줌
  • interpolation = ‘nearest’: 해당 %에 데이터가 없을 때 가장 근접한 데이터 넣어줌
  • [0]일때가 5%니까, [7]일 때 40%, [14]일 때 75%
df_mart.groupby('total_amt_grp')['target'].agg(['sum', 'count'])
# ▶ non-target labeling
df_mart['n_target'] = np.where(df_mart['target']==0, 1, 0)

# ▶ target , non-target count
iv = df_mart.groupby('total_amt_grp')['target', 'n_target'](https://code7ssage.github.io/'target', 'n_target'//).sum().reset_index()

# ▶ good_pct / bad_pct 계산
iv['good_pct'] = iv['target'] / iv['target'].sum()
iv['bad_pct'] = iv['n_target'] / iv['n_target'].sum()

# ▶ t_ratio
iv['t_ratio'] = iv['target'] / (iv['target'] + iv['n_target'])

iv
  • n_target: target이 0이면 1을 넣어주고 아니면 0을 넣어줌
  • good, bad, t 퍼센트 각각 계산
# ▶ Group별 IV 계산
import math

for i in range(len(iv['total_amt_grp'])) :
  iv.loc[i, 'iv'] = (iv.loc[i,'good_pct'] - iv.loc[i,'bad_pct']) * math.log(iv.loc[i,'good_pct']/iv.loc[i,'bad_pct'])

iv
  • IV 계산하면 1loop 종료
# for문 활용 자동화
integer_list = ['total_amt', 'max_amt', 'min_amt', 'total_cnt','max_cnt', 'min_cnt', 'total_qty', 'max_qty', 'min_qty']
iv_df = []

for val in integer_list : 
    # ▶ Binning
    quantile  = df_mart[val].quantile(q=list(np.arange(0.05,1,0.05)), interpolation = 'nearest')

    # ▶ 그룹 초기화 
    df_mart['grp'] = 0

    # ▶ 40%, 75% 기준으로 총 3개 구간화 진행 
    for i in  range(len(df_mart[val])) :
      if df_mart.loc[i, val] <= quantile.iloc[7] :
        df_mart.loc[i, 'grp'] = 1
      elif df_mart.loc[i, val] <= quantile.iloc[14] :
        df_mart.loc[i, 'grp'] = 2
      else : df_mart.loc[i, 'grp'] = 3

    # ▶ non target labeling
    df_mart['n_target'] = np.where(df_mart['target']==0, 1, 0)

    # ▶ target , non-target count
    iv = df_mart.groupby('grp')['target', 'n_target'](https://code7ssage.github.io/'target', 'n_target'//).sum().reset_index()

    # ▶ good_pct / bad_pct 계산
    iv['good_pct'] = iv['target'] / iv['target'].sum()
    iv['bad_pct'] = iv['n_target'] / iv['n_target'].sum()

    # ▶ t_ratio
    iv['t_ratio'] = iv['target'] / (iv['target'] + iv['n_target'])

    iv['val'] = val

    for i in range(len(iv['grp'])) :
      iv.loc[i, 'iv'] = (iv.loc[i,'good_pct'] - iv.loc[i,'bad_pct']) * math.log(iv.loc[i,'good_pct']/iv.loc[i,'bad_pct'])
  
    iv_df.append(iv)

iv_df=pd.concat(iv_df)
iv_df
  • total_amt ~min_qty 까지 1,2,3 bin에 대해 IV값 계산
# ▶ Feature별 전체 IV 값을 sum해서 비교
iv_df.groupby('val')['iv'].sum().sort_values(ascending=False)
  • feature별 IV값 내림차순으로 정리

04. Categoical Feature Engineering


Feature Engineering이란?

  • 모델링의 목적인 성능향상을 위해 Feature를 생성, 선택, 가공하는 일련의 모든 활동

Target 변수와의 의미있는 변수 선택하는 방법

  • 차원이 너무 많은 경우에는 차원을 Domain 지식을 활용하여 축소할 것

IV (Information Value)

  • Feature 하나가 Good(Target)과 Bad(Non-target)을 잘 구분해줄 수 있는지에 대한 정보량을 표현하는 방법론
  • IV 수치가 클수록 Target과 Non-target을 잘 구분할 수 있는 정보량이 많은 Feature이고, IV 수치가 작을수록 정보량이 적은 Feature
  • (Target data 구성비 - Non-target data 구성비) * WoE
  • WoE(Weight of Evidence) = ln(Target data 구성비 / Non-target data 구성비)
  • ln(자연로그)를 취하는 이유? : WoE의 최대, 최소값의 범위를 맞춰주기 위해 (1~∞ , -∞~1)

개념 참조

Catplot

  • Categorical Plot : 색상(hue)과 행(row)등을 동시에 사용하여 3 개 이상의 카테고리 값에 의한 변화를 그려볼 수 있음
  • Categorical data를 파악하는데 용이함
df_mart.head()
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use(['dark_background'])

# ▶ target 기준(hue,색상)으로 구분하여 Plot
sns.catplot(x="Country", hue="target", kind="count",palette="pastel", edgecolor=".6",data=df_mart);
plt.xticks(rotation=-20)

plt.gcf().set_size_inches(25, 3)
  • hue: 파랑: 0, 주황: 1

image

# ▶ EDA value
df_cat = pd.DataFrame(df_mart['Country'].value_counts())
df_cat['ratio'] = df_cat['Country'] / df_cat['Country'].sum()
df_cat
  • UK: 90% 차지 -> 나머지는 묶어도 상관 없을 듯?
# ▶ UK(영국) 이외는 기타국가로 처리
df_mart['Country'] = np.where(df_mart['Country'] == 'United Kingdom', 'UK', 'ETC')
df_mart['Country'].value_counts()
  • np.where == (if, true, false)
# ▶ target , non-target count
iv = df_mart.groupby('Country')['target', 'n_target'](https://code7ssage.github.io/'target', 'n_target'//).sum().reset_index()

# ▶ good_pct /bad_pct 계산
iv['good_pct'] = iv['target'] / iv['target'].sum()
iv['bad_pct'] = iv['n_target'] / iv['n_target'].sum()

# ▶ t_ratio
iv['t_ratio'] = iv['target'] / (iv['target'] + iv['n_target'])

iv['val'] = 'Country'

for i in range(len(iv['Country'])) :
  iv.loc[i, 'iv'] = (iv.loc[i,'good_pct'] - iv.loc[i,'bad_pct']) * math.log(iv.loc[i,'good_pct']/iv.loc[i,'bad_pct'])

iv
# ▶ 많은 정보량을 가지고 있지 않음
iv['iv'].sum()
  • 0.000017.. -> 너무 낮음;
# ▶ 최종 Mart 저장
df_mart = pd.DataFrame(df_mart)
df_mart.to_csv('df_mart.csv', index=False)
  • 마트 저장 필수!

▶ OptimalBinning

!pip install optbinning
# ▶ numeric
# !pip install optbinning
from optbinning import OptimalBinning

integer_list = ['total_amt', 'max_amt', 'min_amt', 'total_cnt','max_cnt', 'min_cnt', 'total_qty', 'max_qty', 'min_qty']
iv_df = []

for i in integer_list :
  variable = i
  x = df_mart[variable].values
  y = df_mart.target
  # max_n_prebins
  optb = OptimalBinning(name=variable, dtype="numerical", solver="cp", max_n_prebins=3)
  optb.fit(x, y)
  # print("split points : ", optb.splits)

  binning_table = optb.binning_table
  v1 = binning_table.build()

  df = pd.DataFrame({'val' : variable,
                     'IV' : [v1.loc['Totals','IV']]})
  iv_df.append(df)

iv_df = pd.concat(iv_df).reset_index(drop=True)
iv_df.sort_values(by=['IV'], ascending = False)


# binning_table.plot(metric="event_rate")
  • x: 열의 데이터 값, y: target
  • numerical 일때는 cp가 디폴트
  • max_n_prebins: 몇개의 bin 만들건지
  • val, IV가 키 인 dict형태로 데이터 프레임 형성
binning_table.plot(metric="event_rate")
  • event rate가 타겟률
  • 아까랑 bining 기준이 달라져 IV값은 달라짐 주의 image
# ▶ Categorical
variable_cat = "Country"
x_cat = df_mart[variable_cat].values
y_cat = df_mart.target

optb = OptimalBinning(name=variable_cat, dtype="categorical", solver="mip",
                      cat_cutoff=0.1)

optb.fit(x_cat, y_cat)
optb.splits

binning_table = optb.binning_table
display(binning_table.build())

binning_table.plot(metric="event_rate")
  • categorical에서는 mip사용
  • cat_cutoff=0.1: 10%미만의 것은 자동적으로 etc로 묶어줌 image

댓글남기기