DataFrame の集約処理 (カテゴリデータ)

Posted on 2020/02/03 in 機械学習 , Updated on: 2020/02/03

はじめに

別記事 DataFrame の集約処理 (数値データ)では、数値データに関する集約処理の方法を説明した。本記事では、同様にHome Credit Default Riskコンペデータを使用して、カテゴリデータに関する集約処理方法を説明する。

コンペ概要
Home Credit社は、信用の積み重ねが足りずに融資を受けることができない顧客にも融資を行う会社で、今回のコンペは債務不履行(デフォルト, default)になる顧客を予測する。
参考: Introduction to Manual Feature Engineering

データの準備

application_train データと、1対多で紐づくデータを含む bureau データを呼び出す。

  • application_train : サンプルID として SK_ID_CURR 列がある。これは一意に決定されるユーザIDで、重複はない。つまり、データの行数と SK_ID_CURR の一意の数は等しい。
  • bureau : 上記SK_ID_CURRで決定される顧客が、過去に行った融資情報を SK_ID_BUREAU として含む。各ユーザはいくつかのSK_ID_BUREAU を持つ。また、過去融資情報が無い顧客も存在しているため、bureau 内のSK_ID_CURR の一意の数は、application_train内のそれよりも少ない。

データを確認する。

In [1]:
import numpy as np
import pandas as pd

train = pd.read_csv('data/application_train.csv')

print('Training data shape:', train.shape)
print('Unique number of SK_ID_CURR in training data:', train.SK_ID_CURR.nunique())
train.head()
Training data shape: (307511, 122)
Unique number of SK_ID_CURR in training data: 307511
Out[1]:
SK_ID_CURR TARGET NAME_CONTRACT_TYPE CODE_GENDER FLAG_OWN_CAR FLAG_OWN_REALTY CNT_CHILDREN AMT_INCOME_TOTAL AMT_CREDIT AMT_ANNUITY ... FLAG_DOCUMENT_18 FLAG_DOCUMENT_19 FLAG_DOCUMENT_20 FLAG_DOCUMENT_21 AMT_REQ_CREDIT_BUREAU_HOUR AMT_REQ_CREDIT_BUREAU_DAY AMT_REQ_CREDIT_BUREAU_WEEK AMT_REQ_CREDIT_BUREAU_MON AMT_REQ_CREDIT_BUREAU_QRT AMT_REQ_CREDIT_BUREAU_YEAR
0 100002 1 Cash loans M N Y 0 202500.0 406597.5 24700.5 ... 0 0 0 0 0.0 0.0 0.0 0.0 0.0 1.0
1 100003 0 Cash loans F N N 0 270000.0 1293502.5 35698.5 ... 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0
2 100004 0 Revolving loans M Y Y 0 67500.0 135000.0 6750.0 ... 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0
3 100006 0 Cash loans F N Y 0 135000.0 312682.5 29686.5 ... 0 0 0 0 NaN NaN NaN NaN NaN NaN
4 100007 0 Cash loans M N Y 0 121500.0 513000.0 21865.5 ... 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0

5 rows × 122 columns

In [2]:
bureau = pd.read_csv('data/bureau.csv')

print('Bureau data shape:', bureau.shape)
print('Unique number of SK_ID_CURR in bureau data:', bureau.SK_ID_CURR.nunique())
print('Unique number of SK_ID_BUREAU in bureau data:', bureau.SK_ID_BUREAU.nunique())
bureau.head()
Bureau data shape: (1716428, 17)
Unique number of SK_ID_CURR in bureau data: 305811
Unique number of SK_ID_BUREAU in bureau data: 1716428
Out[2]:
SK_ID_CURR SK_ID_BUREAU CREDIT_ACTIVE CREDIT_CURRENCY DAYS_CREDIT CREDIT_DAY_OVERDUE DAYS_CREDIT_ENDDATE DAYS_ENDDATE_FACT AMT_CREDIT_MAX_OVERDUE CNT_CREDIT_PROLONG AMT_CREDIT_SUM AMT_CREDIT_SUM_DEBT AMT_CREDIT_SUM_LIMIT AMT_CREDIT_SUM_OVERDUE CREDIT_TYPE DAYS_CREDIT_UPDATE AMT_ANNUITY
0 215354 5714462 Closed currency 1 -497 0 -153.0 -153.0 NaN 0 91323.0 0.0 NaN 0.0 Consumer credit -131 NaN
1 215354 5714463 Active currency 1 -208 0 1075.0 NaN NaN 0 225000.0 171342.0 NaN 0.0 Credit card -20 NaN
2 215354 5714464 Active currency 1 -203 0 528.0 NaN NaN 0 464323.5 NaN NaN 0.0 Consumer credit -16 NaN
3 215354 5714465 Active currency 1 -203 0 NaN NaN NaN 0 90000.0 NaN NaN 0.0 Credit card -16 NaN
4 215354 5714466 Active currency 1 -629 0 1197.0 NaN 77674.5 0 2700000.0 NaN NaN 0.0 Consumer credit -21 NaN

Categorical data

カテゴリ変数は、離散文字列変数であるため、「平均」や「合計」などの数値統計処理を適用できない。代わりに各カテゴリ特徴量の頻度(カウント)の計算を実行する。そして、その値のカウントをその特徴量内での出現回数の合計で正規化した特徴量を作成する。(頻度エンコーディング)

下記に簡単な例を示す。

In [12]:
test_df = pd.DataFrame([[1,1,1,1,2,3,3,3,4,4,4],
                        ['A','A','A','B','B','B','C','C','B','A','A']]).T
test_df = test_df.rename(columns={0:'ID', 1: 'type'}).set_index('ID')

test_df
Out[12]:
type
ID
1 A
1 A
1 A
1 B
2 B
3 B
3 C
3 C
4 B
4 A
4 A
In [19]:
# type 特徴量に One Hot Encoding を実行し、各 ID で出現回数と全体に対する割合を算出
pd.get_dummies(test_df).groupby('ID').agg(['sum', 'mean'])
Out[19]:
type_A type_B type_C
sum mean sum mean sum mean
ID
1 3 0.750000 1 0.250000 0 0.000000
2 0 0.000000 1 1.000000 0 0.000000
3 0 0.000000 1 0.333333 2 0.666667
4 2 0.666667 1 0.333333 0 0.000000

ID=1 では、type A は3回出現しており、Bは1回である。また ID=1の出現回数は 4回なので、各typeの出現割合(正規化データ)は下記の通りになる。

  • A: 3/4 = 0.75
  • B: 1/4 = 0.25
  • C: 0/4 = 0.00

Aggregation (手作業)

実データにて同様の処理を実行する。まず、select_dtypes を使って object 特徴量のみを抜き出し、OneHotEncoding を適用。

In [21]:
categorical = pd.get_dummies(bureau.select_dtypes('object'))

# ID カラムを追加
categorical['SK_ID_CURR'] = bureau['SK_ID_CURR']
categorical.head()
Out[21]:
CREDIT_ACTIVE_Active CREDIT_ACTIVE_Bad debt CREDIT_ACTIVE_Closed CREDIT_ACTIVE_Sold CREDIT_CURRENCY_currency 1 CREDIT_CURRENCY_currency 2 CREDIT_CURRENCY_currency 3 CREDIT_CURRENCY_currency 4 CREDIT_TYPE_Another type of loan CREDIT_TYPE_Car loan ... CREDIT_TYPE_Loan for business development CREDIT_TYPE_Loan for purchase of shares (margin lending) CREDIT_TYPE_Loan for the purchase of equipment CREDIT_TYPE_Loan for working capital replenishment CREDIT_TYPE_Microloan CREDIT_TYPE_Mobile operator loan CREDIT_TYPE_Mortgage CREDIT_TYPE_Real estate loan CREDIT_TYPE_Unknown type of loan SK_ID_CURR
0 0 0 1 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 215354
1 1 0 0 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 215354
2 1 0 0 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 215354
3 1 0 0 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 215354
4 1 0 0 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 215354

5 rows × 24 columns

次に、SK_ID_CURR でグループ化し、集約処理を実行する。

In [25]:
categorical_grouped = categorical.groupby('SK_ID_CURR').agg(['sum', 'mean'])
categorical_grouped.head()
Out[25]:
CREDIT_ACTIVE_Active CREDIT_ACTIVE_Bad debt CREDIT_ACTIVE_Closed CREDIT_ACTIVE_Sold CREDIT_CURRENCY_currency 1 ... CREDIT_TYPE_Microloan CREDIT_TYPE_Mobile operator loan CREDIT_TYPE_Mortgage CREDIT_TYPE_Real estate loan CREDIT_TYPE_Unknown type of loan
sum mean sum mean sum mean sum mean sum mean ... sum mean sum mean sum mean sum mean sum mean
SK_ID_CURR
100001 3 0.428571 0 0.0 4 0.571429 0 0.0 7 1.0 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
100002 2 0.250000 0 0.0 6 0.750000 0 0.0 8 1.0 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
100003 1 0.250000 0 0.0 3 0.750000 0 0.0 4 1.0 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
100004 0 0.000000 0 0.0 2 1.000000 0 0.0 2 1.0 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
100005 2 0.666667 0 0.0 1 0.333333 0 0.0 3 1.0 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0

5 rows × 46 columns

  • sum: 各特徴量の ID ごとの出現回数
  • mean: 各特徴量の ID ごとの正規化された出現回数

次に、前回記事同様にマルチレベルになっているカラムを修正し、名称を変更する。

  • sumcount
  • meancount_norm
In [26]:
group_var = 'SK_ID_CURR'

columns = []

# levels[0] は上段
for col in categorical_grouped.columns.levels[0]:
    
    if col != group_var:
        for stat in ['count', 'count_norm']:
            columns.append('%s_%s' % (col, stat))
        
categorical_grouped.columns = columns
categorical_grouped = categorical_grouped.reset_index()
categorical_grouped.head()
Out[26]:
SK_ID_CURR CREDIT_ACTIVE_Active_count CREDIT_ACTIVE_Active_count_norm CREDIT_ACTIVE_Bad debt_count CREDIT_ACTIVE_Bad debt_count_norm CREDIT_ACTIVE_Closed_count CREDIT_ACTIVE_Closed_count_norm CREDIT_ACTIVE_Sold_count CREDIT_ACTIVE_Sold_count_norm CREDIT_CURRENCY_currency 1_count ... CREDIT_TYPE_Microloan_count CREDIT_TYPE_Microloan_count_norm CREDIT_TYPE_Mobile operator loan_count CREDIT_TYPE_Mobile operator loan_count_norm CREDIT_TYPE_Mortgage_count CREDIT_TYPE_Mortgage_count_norm CREDIT_TYPE_Real estate loan_count CREDIT_TYPE_Real estate loan_count_norm CREDIT_TYPE_Unknown type of loan_count CREDIT_TYPE_Unknown type of loan_count_norm
0 100001 3 0.428571 0 0.0 4 0.571429 0 0.0 7 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
1 100002 2 0.250000 0 0.0 6 0.750000 0 0.0 8 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
2 100003 1 0.250000 0 0.0 3 0.750000 0 0.0 4 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
3 100004 0 0.000000 0 0.0 2 1.000000 0 0.0 2 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
4 100005 2 0.666667 0 0.0 1 0.333333 0 0.0 3 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0

5 rows × 47 columns

Aggregation (関数使って)

同様の作業を実行する関数を定義する。これにより他の dataframe にも同じ操作を実施したい時に再利用が可能になる。

In [46]:
# df          : 処理する dataframe
# group_var   : 集約する起点となる特徴量名 (ID名)
# df_name     : dataframe名 (string型)

def count_categorical(df, group_var, df_name):
    
    categorical = pd.get_dummies(df.select_dtypes('object'))
    categorical[group_var] = df[group_var]
    
    categorical = categorical.groupby(group_var).agg(['sum', 'mean']).reset_index()
    
    columns = [group_var]
    
    for col in categorical.columns.levels[0]:
        if col != group_var:
            for stat in ['count', 'count_norm']:
                columns.append('%s_%s_%s' % (df_name, col, stat))
            
    categorical.columns = columns
    return categorical
In [47]:
categorical_grouped_by_func = count_categorical(bureau, 'SK_ID_CURR', 'bureau')
print(categorical_grouped_by_func.shape)
categorical_grouped_by_func.head()
(305811, 47)
Out[47]:
SK_ID_CURR bureau_CREDIT_ACTIVE_Active_count bureau_CREDIT_ACTIVE_Active_count_norm bureau_CREDIT_ACTIVE_Bad debt_count bureau_CREDIT_ACTIVE_Bad debt_count_norm bureau_CREDIT_ACTIVE_Closed_count bureau_CREDIT_ACTIVE_Closed_count_norm bureau_CREDIT_ACTIVE_Sold_count bureau_CREDIT_ACTIVE_Sold_count_norm bureau_CREDIT_CURRENCY_currency 1_count ... bureau_CREDIT_TYPE_Microloan_count bureau_CREDIT_TYPE_Microloan_count_norm bureau_CREDIT_TYPE_Mobile operator loan_count bureau_CREDIT_TYPE_Mobile operator loan_count_norm bureau_CREDIT_TYPE_Mortgage_count bureau_CREDIT_TYPE_Mortgage_count_norm bureau_CREDIT_TYPE_Real estate loan_count bureau_CREDIT_TYPE_Real estate loan_count_norm bureau_CREDIT_TYPE_Unknown type of loan_count bureau_CREDIT_TYPE_Unknown type of loan_count_norm
0 100001 3 0.428571 0 0.0 4 0.571429 0 0.0 7 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
1 100002 2 0.250000 0 0.0 6 0.750000 0 0.0 8 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
2 100003 1 0.250000 0 0.0 3 0.750000 0 0.0 4 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
3 100004 0 0.000000 0 0.0 2 1.000000 0 0.0 2 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
4 100005 2 0.666667 0 0.0 1 0.333333 0 0.0 3 ... 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0

5 rows × 47 columns

手作業で得られた、集計済み dataframe と同じ dataframe が得られた。