DataFrame の集約処理 (数値データ)

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

はじめに

pandas の groupby メソッドを用いたデータの aggregation (集約) 方法の説明。さらに集約したデータを他の table へ結合し、新たな特徴量を含めたデータで目的変数との相関係数を調べる。本記事では、数値データのみの集約処理を説明する。カテゴリデータに関しては別記事を参照。

テーブルコンペにおいて、一意のサンプルID が付与されている training データとは別に、サンプルIDに対して 1対多 で紐づけられている table データを処理する際に良く用いられる。kaggle の 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

Aggregation (手作業)

bureau データに対して集約処理を実行する。groupby メソッドで、顧客IDでグループ化し、agg関数を用いて集計する。ここでは、「頻度」,「平均」,「合計」を適用するが、任意の関数を渡すこともできる。また、pandas の agg 関数は数値データのみに適用される。

In [3]:
# SK_ID_BUREAU も数値データとして扱われるので、除外する
bureau_agg = bureau.drop(columns=['SK_ID_BUREAU'])

bureau_agg = bureau_agg.groupby('SK_ID_CURR', as_index=False).agg(['count', 'mean', 'sum']).reset_index()
bureau_agg.head()
Out[3]:
SK_ID_CURR DAYS_CREDIT CREDIT_DAY_OVERDUE DAYS_CREDIT_ENDDATE ... AMT_CREDIT_SUM_LIMIT AMT_CREDIT_SUM_OVERDUE DAYS_CREDIT_UPDATE AMT_ANNUITY
count mean sum count mean sum count mean sum ... sum count mean sum count mean sum count mean sum
0 100001 7 -735.000000 -5145 7 0.0 0 7 82.428571 577.0 ... 0.000 7 0.0 0.0 7 -93.142857 -652 7 3545.357143 24817.5
1 100002 8 -874.000000 -6992 8 0.0 0 6 -349.000000 -2094.0 ... 31988.565 8 0.0 0.0 8 -499.875000 -3999 7 0.000000 0.0
2 100003 4 -1400.750000 -5603 4 0.0 0 4 -544.500000 -2178.0 ... 810000.000 4 0.0 0.0 4 -816.000000 -3264 0 NaN 0.0
3 100004 2 -867.000000 -1734 2 0.0 0 2 -488.500000 -977.0 ... 0.000 2 0.0 0.0 2 -532.000000 -1064 0 NaN 0.0
4 100005 3 -190.666667 -572 3 0.0 0 3 439.333333 1318.0 ... 0.000 3 0.0 0.0 3 -54.333333 -163 3 1420.500000 4261.5

5 rows × 37 columns

これにより、各数値特徴量の「頻度」,「平均」,「合計」特徴量が作成された。ただ、カラム名が二段(level 2)になっているため、下記の操作でカラムの level を下げる必要がある。

In [4]:
# column 名のリストを準備
columns = ['SK_ID_CURR']

# カラム名を一つ一つ読み出す
for col in bureau_agg.columns.levels[0]:
    
    # SK_ID_CURR は飛ばす
    if col != 'SK_ID_CURR':
        
        for stat in bureau_agg.columns.levels[1][:-1]:
            # 変数と統計量の新しい名前を生成し、リストへ追加
            columns.append('bureau_%s_%s' % (col, stat))
            
# 作成したカラム名を dataframe へ適用
bureau_agg.columns = columns

print('bureau_agg shape:', bureau_agg.shape)
bureau_agg.head()
bureau_agg shape: (305811, 37)
Out[4]:
SK_ID_CURR bureau_DAYS_CREDIT_count bureau_DAYS_CREDIT_mean bureau_DAYS_CREDIT_sum bureau_CREDIT_DAY_OVERDUE_count bureau_CREDIT_DAY_OVERDUE_mean bureau_CREDIT_DAY_OVERDUE_sum bureau_DAYS_CREDIT_ENDDATE_count bureau_DAYS_CREDIT_ENDDATE_mean bureau_DAYS_CREDIT_ENDDATE_sum ... bureau_AMT_CREDIT_SUM_LIMIT_sum bureau_AMT_CREDIT_SUM_OVERDUE_count bureau_AMT_CREDIT_SUM_OVERDUE_mean bureau_AMT_CREDIT_SUM_OVERDUE_sum bureau_DAYS_CREDIT_UPDATE_count bureau_DAYS_CREDIT_UPDATE_mean bureau_DAYS_CREDIT_UPDATE_sum bureau_AMT_ANNUITY_count bureau_AMT_ANNUITY_mean bureau_AMT_ANNUITY_sum
0 100001 7 -735.000000 -5145 7 0.0 0 7 82.428571 577.0 ... 0.000 7 0.0 0.0 7 -93.142857 -652 7 3545.357143 24817.5
1 100002 8 -874.000000 -6992 8 0.0 0 6 -349.000000 -2094.0 ... 31988.565 8 0.0 0.0 8 -499.875000 -3999 7 0.000000 0.0
2 100003 4 -1400.750000 -5603 4 0.0 0 4 -544.500000 -2178.0 ... 810000.000 4 0.0 0.0 4 -816.000000 -3264 0 NaN 0.0
3 100004 2 -867.000000 -1734 2 0.0 0 2 -488.500000 -977.0 ... 0.000 2 0.0 0.0 2 -532.000000 -1064 0 NaN 0.0
4 100005 3 -190.666667 -572 3 0.0 0 3 439.333333 1318.0 ... 0.000 3 0.0 0.0 3 -54.333333 -163 3 1420.500000 4261.5

5 rows × 37 columns

上記作業で、例えば DAYS_CREDIT 内の count 特徴量が、新たに bureau_DAYS_CREDIT_count という名称になり、一段の名前になっていることがわかる。これで application_trainSK_ID_CURR で紐づけて結合することができる。

ここで、burea_agg に含まれていない SK_ID_CURR の集約特徴量部は、NaN になる。(exp. SK_ID_CURR=100006)

In [5]:
print('Traning data shape before merged:', train.shape)

train = train.merge(bureau_agg, on='SK_ID_CURR', how='left')

print('Training data shape after merged:', train.shape)
train.head()
Traning data shape before merged: (307511, 122)
Training data shape after merged: (307511, 158)
Out[5]:
SK_ID_CURR TARGET NAME_CONTRACT_TYPE CODE_GENDER FLAG_OWN_CAR FLAG_OWN_REALTY CNT_CHILDREN AMT_INCOME_TOTAL AMT_CREDIT AMT_ANNUITY ... bureau_AMT_CREDIT_SUM_LIMIT_sum bureau_AMT_CREDIT_SUM_OVERDUE_count bureau_AMT_CREDIT_SUM_OVERDUE_mean bureau_AMT_CREDIT_SUM_OVERDUE_sum bureau_DAYS_CREDIT_UPDATE_count bureau_DAYS_CREDIT_UPDATE_mean bureau_DAYS_CREDIT_UPDATE_sum bureau_AMT_ANNUITY_count bureau_AMT_ANNUITY_mean bureau_AMT_ANNUITY_sum
0 100002 1 Cash loans M N Y 0 202500.0 406597.5 24700.5 ... 31988.565 8.0 0.0 0.0 8.0 -499.875 -3999.0 7.0 0.0 0.0
1 100003 0 Cash loans F N N 0 270000.0 1293502.5 35698.5 ... 810000.000 4.0 0.0 0.0 4.0 -816.000 -3264.0 0.0 NaN 0.0
2 100004 0 Revolving loans M Y Y 0 67500.0 135000.0 6750.0 ... 0.000 2.0 0.0 0.0 2.0 -532.000 -1064.0 0.0 NaN 0.0
3 100006 0 Cash loans F N Y 0 135000.0 312682.5 29686.5 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 100007 0 Cash loans M N Y 0 121500.0 513000.0 21865.5 ... 0.000 1.0 0.0 0.0 1.0 -783.000 -783.0 0.0 NaN 0.0

5 rows × 158 columns

新たに作成された特徴量36個が、dataframe の右側に追加されている。

相関を見る

ここから、新たに作成した特徴量と TARGET (目的変数) との相関を見ることで、機械学習モデルに有用な特徴量が作成されたかどうかを調べることができる。

In [6]:
corrs = []

# TARGET と各特徴量の相関係数を計算
for col in columns:
    corr = train['TARGET'].corr(train[col])
    corrs.append((col, corr))

# 相関係数の絶対値の降順で上位 10 個を表示
corrs = sorted(corrs, key=lambda x: abs(x[1]), reverse=True)
corrs[:10]
Out[6]:
[('bureau_DAYS_CREDIT_mean', 0.089728967219981),
 ('bureau_DAYS_CREDIT_UPDATE_mean', 0.06892735266968684),
 ('bureau_DAYS_CREDIT_ENDDATE_sum', 0.053734895601020585),
 ('bureau_DAYS_ENDDATE_FACT_mean', 0.05319962585758622),
 ('bureau_DAYS_ENDDATE_FACT_sum', 0.048853502611115936),
 ('bureau_DAYS_CREDIT_ENDDATE_mean', 0.04698275433483553),
 ('bureau_DAYS_CREDIT_sum', 0.04199982481484667),
 ('bureau_DAYS_CREDIT_UPDATE_sum', 0.0414036353530601),
 ('bureau_DAYS_ENDDATE_FACT_count', -0.03049230665332547),
 ('bureau_AMT_CREDIT_SUM_mean', -0.019956561902304555)]

Aggregation (関数使って)

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

In [8]:
# df          : 処理する dataframe
# group_var   : 集約する起点となる特徴量名 (ID名)
# df_name     : dataframe名 (string型)
# agg_list    : 処理する統計量のリスト
# remove_cols : 処理しない特徴量リスト

def agg_numeric(df, group_var, df_name, agg_list, remove_cols):
    
    # ID と不要な特徴量を削除する前に、ID列を抜き出し
    group_ids = df[group_var]
    df = df.drop(columns=remove_cols)
    df = df.drop(columns=group_var)
    numeric_df = df.select_dtypes('number')
    numeric_df.loc[:, group_var] = group_ids
    
    # aggregation 処理
    agg = numeric_df.groupby(group_var).agg(agg_list).reset_index()
    
    # カラム名を修正
    columns = [group_var]
    for col in agg.columns.levels[0]:
        if col != group_var:
            for stat in agg.columns.levels[1][:-1]:
                columns.append('%s_%s_%s' % (df_name, col, stat))
                
    agg.columns = columns
    
    return agg

処理する統計リストを定義して、手作業時と同様の集約処理を実行する。同じ集約済みデータが生成されていることがわかる。

In [10]:
agg_list = ['count', 'mean', 'sum']

bureau_agg_by_func = agg_numeric(bureau, group_var='SK_ID_CURR', df_name='bureau',
                                 agg_list=agg_list, remove_cols=['SK_ID_BUREAU'])
print('bureau_agg_by_func shape:', bureau_agg.shape)
bureau_agg_by_func.head()
bureau_agg_by_func shape: (305811, 37)
Out[10]:
SK_ID_CURR bureau_DAYS_CREDIT_count bureau_DAYS_CREDIT_mean bureau_DAYS_CREDIT_sum bureau_CREDIT_DAY_OVERDUE_count bureau_CREDIT_DAY_OVERDUE_mean bureau_CREDIT_DAY_OVERDUE_sum bureau_DAYS_CREDIT_ENDDATE_count bureau_DAYS_CREDIT_ENDDATE_mean bureau_DAYS_CREDIT_ENDDATE_sum ... bureau_AMT_CREDIT_SUM_LIMIT_sum bureau_AMT_CREDIT_SUM_OVERDUE_count bureau_AMT_CREDIT_SUM_OVERDUE_mean bureau_AMT_CREDIT_SUM_OVERDUE_sum bureau_DAYS_CREDIT_UPDATE_count bureau_DAYS_CREDIT_UPDATE_mean bureau_DAYS_CREDIT_UPDATE_sum bureau_AMT_ANNUITY_count bureau_AMT_ANNUITY_mean bureau_AMT_ANNUITY_sum
0 100001 7 -735.000000 -5145 7 0.0 0 7 82.428571 577.0 ... 0.000 7 0.0 0.0 7 -93.142857 -652 7 3545.357143 24817.5
1 100002 8 -874.000000 -6992 8 0.0 0 6 -349.000000 -2094.0 ... 31988.565 8 0.0 0.0 8 -499.875000 -3999 7 0.000000 0.0
2 100003 4 -1400.750000 -5603 4 0.0 0 4 -544.500000 -2178.0 ... 810000.000 4 0.0 0.0 4 -816.000000 -3264 0 NaN 0.0
3 100004 2 -867.000000 -1734 2 0.0 0 2 -488.500000 -977.0 ... 0.000 2 0.0 0.0 2 -532.000000 -1064 0 NaN 0.0
4 100005 3 -190.666667 -572 3 0.0 0 3 439.333333 1318.0 ... 0.000 3 0.0 0.0 3 -54.333333 -163 3 1420.500000 4261.5

5 rows × 37 columns